From 7f03b238d0b3ff54dcc8c33e80507591d7002179 Mon Sep 17 00:00:00 2001 From: Sabique A Khan Date: Mon, 8 Oct 2018 23:44:27 +0530 Subject: [PATCH 001/729] Minor document updates (#128) --- CONTRIBUTING.md | 2 +- Guidelines.md | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45b51a63..98a286d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Contributing to the Microsoft REST API Guidelines The Microsoft REST API Guidelines is a Microsoft-wide initiative to develop consistent design guidelines for REST APIs. The initiative requires input and feedback from a variety of individuals both inside and outside of Microsoft. -To provide feedback, please follow the guidance in this document. Please note that these are just guidelines, not rules. Use your best judgment and feel free to propose changes to anything in this repository, including the contribution guidance. +To provide feedback, please follow the guidance in this document. Please note that these are just guidelines, not rules. Use your best judgment and feel free to propose changes to anything in this repository, including the contribution guidelines. Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. - [Creating issues](#creating-issues) diff --git a/Guidelines.md b/Guidelines.md index 327c17d4..9dcf8ebd 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -206,7 +206,7 @@ Private or internal APIs SHOULD also try to follow these guidelines because inte Consistency is valuable to not only external customers but also internal service consumers, and these guidelines offer best practices useful for any service. There are legitimate reasons for exemption from these guidelines. -Obviously a REST service that implements or must interoperate with some externally defined REST API must be compatible with that API and not necessarily these guidelines. +Obviously, a REST service that implements or must interoperate with some externally defined REST API must be compatible with that API and not necessarily these guidelines. Some services MAY also have special performance needs that require a different format, such as a binary protocol. ### 4.2. Guidelines for existing services and versioning of services diff --git a/README.md b/README.md index bf2bd40f..f3c1094f 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,6 @@ The [Microsoft REST API Guidelines](Guidelines.md), as a design principle, encou This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## This repository -This repository contains a collection of documents and related materials supporting the overall Microsoft REST API Guidelines initiative. To contribute to this repository, please see the [contribution guidance][contribution-guidance]. +This repository contains a collection of documents and related materials supporting the overall Microsoft REST API Guidelines initiative. To contribute to this repository, please see the [contribution guidelines][contribution-guidance]. [contribution-guidance]: CONTRIBUTING.md From 3ab520a5d69f84ed869a7f158e1a2fa25870fd58 Mon Sep 17 00:00:00 2001 From: Shaun Hare Date: Mon, 8 Oct 2018 19:17:08 +0100 Subject: [PATCH 002/729] Update Guidelines.md (#126) Associate note - by escaping character, fix capitalisation on Faults --- Guidelines.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Guidelines.md b/Guidelines.md index 9dcf8ebd..ff26e084 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -179,12 +179,12 @@ The benefits of consistency accrue in aggregate as well; consistency allows team These guidelines aim to achieve the following: - Define consistent practices and patterns for all API endpoints across Microsoft. -- Adhere as closely as possible to accepted REST/HTTP best practices in the industry at-large.* +- Adhere as closely as possible to accepted REST/HTTP best practices in the industry at-large. [\*] - Make accessing Microsoft Services via REST interfaces easy for all application developers. - Allow service developers to leverage the prior work of other services to implement, test and document REST endpoints defined consistently. - Allow for partners (e.g., non-Microsoft entities) to use these guidelines for their own REST endpoint design. -*Note: The guidelines are designed to align with building services which comply with the REST architectural style, though they do not address or require building services that follow the REST constraints. +[\*] Note: The guidelines are designed to align with building services which comply with the REST architectural style, though they do not address or require building services that follow the REST constraints. The term "REST" is used throughout this document to mean services that are in the spirit of REST rather than adhering to REST by the book.* ### 3.1. Recommended reading @@ -252,7 +252,7 @@ Services that expose long operations MUST track "Time to Complete" metrics aroun ### 5.5. Long running API faults For a Long Running API, it's possible for both the initial request to begin the operation and the request to retrieve the results to technically work (each passing back a 200), but for the underlying operation to have failed. -Long Running faults MUST roll up as Faults into the overall Availability metrics. +Long Running faults MUST roll up as faults into the overall Availability metrics. ## 6. Client guidance To ensure the best possible experience for clients talking to a REST service, clients SHOULD adhere to the following best practices: From 057193adba3beb883a7951d070024445ba970f6f Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Mon, 8 Oct 2018 13:18:35 -0500 Subject: [PATCH 003/729] Change MAY to might (#120) In context, the word `may` here is not describing an optional activity for the implementer (the normal use of the capitalized word per [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) ). Rather it is describing a possible consequence as justification for the guideline. It's saying "because doing so [might, could] make the value unreadable". --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index ff26e084..0c3f6c9d 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -516,7 +516,7 @@ Services can avoid breaking changes by adding new error codes to "innererror" in The value for the "message" name/value pair MUST be a human-readable representation of the error. It is intended as an aid to developers and is not suitable for exposure to end users. Services wanting to expose a suitable message for end users MUST do so through an [annotation][odata-json-annotations] or custom property. -Services SHOULD NOT localize "message" for the end user, because doing so MAY make the value unreadable to the app developer who may be logging the value, as well as make the value less searchable on the Internet. +Services SHOULD NOT localize "message" for the end user, because doing so might make the value unreadable to the app developer who may be logging the value, as well as make the value less searchable on the Internet. The value for the "target" name/value pair is the target of the particular error (e.g., the name of the property in error). From 6673b3bb4a915cd634a123c33dce516bbafb6e96 Mon Sep 17 00:00:00 2001 From: Javier Carcamo Date: Mon, 8 Oct 2018 19:19:21 +0100 Subject: [PATCH 004/729] Update Guidelines.md (#123) --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index 0c3f6c9d..c6df700a 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -379,7 +379,7 @@ Because PUT is defined as a complete replacement of the content, it is dangerous Clients that do not understand (and hence ignore) properties on a resource are not likely to provide them on a PUT when trying to update a resource, hence such properties could be inadvertently removed. Services MAY optionally support PUT to update existing resources, but if they do they MUST use replacement semantics (that is, after the PUT, the resource's properties MUST match what was provided in the request, including deleting any server properties that were not provided). -Under UPSERT semantics, a PATCH call to a nonexistent resource is handled by the server as a "create," and a PATCH call to an existing resource is handled as an "update." To ensure that an update request is not treated as a create or vice-versa, the client MAY specify precondition HTTP headers in the request. +Under UPSERT semantics, a PATCH call to a non-existent resource is handled by the server as a "create," and a PATCH call to an existing resource is handled as an "update." To ensure that an update request is not treated as a create or vice-versa, the client MAY specify precondition HTTP headers in the request. The service MUST NOT treat a PATCH request as an insert if it contains an If-Match header and MUST NOT treat a PATCH request as an update if it contains an If-None-Match header with a value of "*". If a service does not support UPSERT, then a PATCH call against a resource that does not exist MUST result in an HTTP "409 Conflict" error. From af136a0285831f4d3f0029e02ff32d8fe028fbc7 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Wed, 10 Oct 2018 11:28:11 -0700 Subject: [PATCH 005/729] Update branch to specify PRs should be against vNext. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 98a286d7..bc359c97 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,7 +81,7 @@ Content-Type: application/json - Reference issues and pull requests liberally ## Pull requests -Pull requests serve as the primary mechanism by which contributions are proposed and accepted. We recommend creating a [topic branch][topic-branch] and sending a pull request to the `master` branch from the topic branch. For additional guidance, read through the [GitHub Flow Guide][github-flow-guide]. +Pull requests serve as the primary mechanism by which contributions are proposed and accepted. We recommend creating a [topic branch][topic-branch] and sending a pull request to the `vNext` branch from the topic branch. For additional guidance, read through the [GitHub Flow Guide][github-flow-guide]. Be prepared to address feedback on your pull request and iterate if necessary. From a2b478d66e67188ccb172b204c0981941f7bb5c4 Mon Sep 17 00:00:00 2001 From: Thomas Hughes Date: Wed, 10 Oct 2018 17:42:49 -0400 Subject: [PATCH 006/729] Add link to consistency guidelines in README (#134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reword to be better reflect RFC 2119. (#73) Section 7.4.3 used the capitalized verb "MAY" without the semantics imposed by RFC 2119. The offending phrase is simply describing a situation that can arise in the real world, rather than a requirement of the specification. The requirement put forth in this specification is defined in the next sentence using the “MUST” verb. * Fix formatting of contibutor table (#81) Probably caused by different markdown implementation in github. * Add link to consistency fundamentals to README This is a quality of life add to the README to link directly to the consistency fundamentals section of the guidelines. --- Guidelines.md | 10 +++++----- README.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Guidelines.md b/Guidelines.md index c6df700a..7854604a 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -2,11 +2,11 @@ ## Microsoft REST API Guidelines Working Group -| | | | -|:-----------------------------|:---------------------------------------|:-----------------------------------------| -| Dave Campbell (CTO C+E) | Rick Rashid (CTO ASG) | John Shewchuk (Technical Fellow, TED HQ) | -| Mark Russinovich (CTO Azure) | Steve Lucco (Technical Fellow, DevDiv) | Murali Krishnaprasad (Azure App Plat) | -| Rob Howard (ASG) | Peter Torr (OSG) | Chris Mullins (ASG) | +Name | Name | Name | +---------------------------- | -------------------------------------- | ---------------------------------------- +Dave Campbell (CTO C+E) | Rick Rashid (CTO ASG) | John Shewchuk (Technical Fellow, TED HQ) +Mark Russinovich (CTO Azure) | Steve Lucco (Technical Fellow, DevDiv) | Murali Krishnaprasad (Azure App Plat) +Rob Howard (ASG) | Peter Torr (OSG) | Chris Mullins (ASG)
Document editors: John Gossman (C+E), Chris Mullins (ASG), Gareth Jones (ASG), Rob Dolin (C+E), Mark Stafford (C+E)
diff --git a/README.md b/README.md index f3c1094f..97554cce 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Microsoft REST API Guidelines -The [Microsoft REST API Guidelines](Guidelines.md), as a design principle, encourages application developers to have resources accessible to them via a RESTful HTTP interface. To provide the smoothest possible experience for developers on platforms following the Microsoft REST API Guidelines, REST APIs SHOULD follow consistent design guidelines to make using them easy and intuitive. +The [Microsoft REST API Guidelines](Guidelines.md), as a design principle, encourages application developers to have resources accessible to them via a RESTful HTTP interface. To provide the smoothest possible experience for developers on platforms following the Microsoft REST API Guidelines, REST APIs SHOULD follow [consistent design guidelines](https://github.com/IAmHughes/api-guidelines/blob/vNext/Guidelines.md#7-consistency-fundamentals) to make using them easy and intuitive. ## Code of Conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. From 056650c803592ff611c6c9dc5369e674d1f74135 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Wed, 10 Oct 2018 14:43:52 -0700 Subject: [PATCH 007/729] Remove absolute link, --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97554cce..07ddd3fb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Microsoft REST API Guidelines -The [Microsoft REST API Guidelines](Guidelines.md), as a design principle, encourages application developers to have resources accessible to them via a RESTful HTTP interface. To provide the smoothest possible experience for developers on platforms following the Microsoft REST API Guidelines, REST APIs SHOULD follow [consistent design guidelines](https://github.com/IAmHughes/api-guidelines/blob/vNext/Guidelines.md#7-consistency-fundamentals) to make using them easy and intuitive. +The [Microsoft REST API Guidelines](Guidelines.md), as a design principle, encourages application developers to have resources accessible to them via a RESTful HTTP interface. To provide the smoothest possible experience for developers on platforms following the Microsoft REST API Guidelines, REST APIs SHOULD follow [consistent design guidelines](Guidelines.md#7-consistency-fundamentals) to make using them easy and intuitive. ## Code of Conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. From f33d9409cec0fae1508eaedb5407e8c9e9a6ce7e Mon Sep 17 00:00:00 2001 From: John Gossman Date: Fri, 11 Jan 2019 14:16:26 -0800 Subject: [PATCH 008/729] Fix copy/paste error (#169) LGTM --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index 7854604a..135867d0 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -1715,7 +1715,7 @@ When a caller has made too many calls 4. Services MAY return RateLimit headers that document the limit or quota that has been exceeded 5. Services MAY return RateLimit-Limit: the number of calls the client is allowed to make in a time window 6. Services MAY return RateLimit-Remaining: the number of calls remaining in the time window -7. Services MAY return RateLimit-Reset: the number of calls the client is allowed to make in a time window +7. Services MAY return RateLimit-Reset: the time at which the window resets in UTC epoch seconds 8. Services MAY return other service specific RateLimit headers as appropriate for more detailed information or specific limits or quotas #### 14.4.3. Overloaded services From d917c7a0f0b09c9308fe9432384f8f6d761a1bc0 Mon Sep 17 00:00:00 2001 From: Jared York Date: Fri, 11 Jan 2019 17:24:36 -0500 Subject: [PATCH 009/729] Removed some spelling mistakes, enhanced readability. (#165) Thanks Jared. --- Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Guidelines.md b/Guidelines.md index 135867d0..9ee5e0c0 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -210,7 +210,7 @@ Obviously, a REST service that implements or must interoperate with some externa Some services MAY also have special performance needs that require a different format, such as a binary protocol. ### 4.2. Guidelines for existing services and versioning of services -We do not recommend making a breaking change to a service that pre-dates these guidelines simply for compliance sake. +We do not recommend making a breaking change to a service that predates these guidelines simply for compliance sake. The service SHOULD try to become compliant at the next version release when compatibility is being broken anyway. When a service adds a new API, that API SHOULD be consistent with the other APIs of the same version. So if a service was written against version 1.0 of the guidelines, new APIs added incrementally to the service SHOULD also follow version 1.0. The service can then upgrade to align with the latest version of the guidelines at the service's next major release. @@ -379,7 +379,7 @@ Because PUT is defined as a complete replacement of the content, it is dangerous Clients that do not understand (and hence ignore) properties on a resource are not likely to provide them on a PUT when trying to update a resource, hence such properties could be inadvertently removed. Services MAY optionally support PUT to update existing resources, but if they do they MUST use replacement semantics (that is, after the PUT, the resource's properties MUST match what was provided in the request, including deleting any server properties that were not provided). -Under UPSERT semantics, a PATCH call to a non-existent resource is handled by the server as a "create," and a PATCH call to an existing resource is handled as an "update." To ensure that an update request is not treated as a create or vice-versa, the client MAY specify precondition HTTP headers in the request. +Under UPSERT semantics, a PATCH call to a nonexistent resource is handled by the server as a "create," and a PATCH call to an existing resource is handled as an "update." To ensure that an update request is not treated as a create or vice versa, the client MAY specify precondition HTTP headers in the request. The service MUST NOT treat a PATCH request as an insert if it contains an If-Match header and MUST NOT treat a PATCH request as an update if it contains an If-None-Match header with a value of "*". If a service does not support UPSERT, then a PATCH call against a resource that does not exist MUST result in an HTTP "409 Conflict" error. From 932db880d08c41f24492dc263232ea6724fb9214 Mon Sep 17 00:00:00 2001 From: Ryan Michalec Date: Fri, 11 Jan 2019 14:27:47 -0800 Subject: [PATCH 010/729] Add Git instructions (#164) Thanks a3 --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc359c97..406acbed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,8 @@ Please note that this project is released with a [Contributor Code of Conduct][c This is the repository for Microsoft REST API Guidelines documentation only. Please ensure that you are opening issues in the right repository. ## Recommended setup for contributing +- Fork this repository on GitHub +- Install [Git][git] to your computer and clone your new forked repository - Install [Atom][atom], [VS Code][vscode], or your favorite editor - Install [markdown-toc package][markdown-toc] @@ -96,3 +98,4 @@ Be prepared to address feedback on your pull request and iterate if necessary. [atom]: http://atom.io [markdown-toc]: https://atom.io/packages/markdown-toc [vscode]: https://code.visualstudio.com/ +[git]: https://git-scm.com/ From a3ed5dac2b5491cd7ed23c0c86bcfa7d92c26fd2 Mon Sep 17 00:00:00 2001 From: David McCarthy Date: Fri, 11 Jan 2019 22:36:49 +0000 Subject: [PATCH 011/729] Updating some spelling/grammar of guidelines.md (#158) Thanks David. --- Guidelines.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Guidelines.md b/Guidelines.md index 9ee5e0c0..4d49b2db 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -170,7 +170,7 @@ This document establishes the guidelines Microsoft REST APIs SHOULD follow so RE Developers access most Microsoft Cloud Platform resources via HTTP interfaces. Although each service typically provides language-specific frameworks to wrap their APIs, all of their operations eventually boil down to HTTP requests. Microsoft must support a wide range of clients and services and cannot rely on rich frameworks being available for every development environment. -Thus a goal of these guidelines is to ensure Microsoft REST APIs can be easily and consistently consumed by any client with basic HTTP support. +Thus, a goal of these guidelines is to ensure Microsoft REST APIs can be easily and consistently consumed by any client with basic HTTP support. To provide the smoothest possible experience for developers, it's important to have these APIs follow consistent design guidelines, thus making using them easy and intuitive. This document establishes the guidelines to be followed by Microsoft REST API developers for developing such APIs consistently. @@ -227,7 +227,7 @@ To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0 As part of onboarding to Microsoft REST API Guidelines, services MUST comply with the taxonomy defined below. ### 5.1. Errors -Errors, or more specifically Service Errors, are defined as a client passing invalid data to the service and the service _correctly_ rejecting that data. +Errors, or more specifically Service Errors, are defined as a client passing invalid data to the service and the service _correctly_ rejecting that data. Examples include invalid credentials, incorrect parameters, unknown version IDs, or similar. These are generally "4xx" HTTP error codes and are the result of a client passing incorrect or invalid data. @@ -315,7 +315,7 @@ Here are some sources for determining what target clients support: * [http://stackoverflow.com/a/417184](http://stackoverflow.com/a/417184) * [https://blogs.msdn.microsoft.com/ieinternals/2014/08/13/url-length-limits/](https://blogs.msdn.microsoft.com/ieinternals/2014/08/13/url-length-limits/) -Also note that some technology stacks have hard and adjustable url limits, so keep this in mind as you design your services. +Also note that some technology stacks have hard and adjustable URL limits, so keep this in mind as you design your services. ### 7.3. Canonical identifier In addition to friendly URLs, resources that can be moved or be renamed SHOULD expose a URL that contains a unique stable identifier. @@ -493,7 +493,7 @@ Accept: application/json ``` #### 7.10.2. Error condition responses -For nonsuccess conditions, developers SHOULD be able to write one piece of code that handles errors consistently across different Microsoft REST API Guidelines services. +For non-success conditions, developers SHOULD be able to write one piece of code that handles errors consistently across different Microsoft REST API Guidelines services. This allows building of simple and reliable infrastructure to handle exceptions as a separate flow from successful responses. The following is based on the OData v4 JSON spec. However, it is very generic and does not require specific OData constructs. @@ -1101,7 +1101,7 @@ Primitive values MUST be serialized to JSON following the rules of [RFC4627][rfc ### 11.2. Guidelines for dates and times #### 11.2.1. Producing dates Services MUST produce dates using the `DateLiteral` format, and SHOULD use the `Iso8601Literal` format unless there are compelling reasons to do otherwise. -Services that do use the `StructuredDateLiteral` format MUST NOT produce dates using the `T` kind unless BOTH the additional precision is REQUIRED and ECMAScript clients are explicitly unsupported. +Services that do use the `StructuredDateLiteral` format MUST NOT produce dates using the `T` kind unless BOTH the additional precision is REQUIRED, and ECMAScript clients are explicitly unsupported. (Non-Normative statement: When deciding which particular `DateKind` to standardize on, the approximate order of preference is `E, C, U, W, O, X, I, T`. This optimizes for ECMAScript, .NET, and C++ programmers, in that order.) @@ -1336,7 +1336,7 @@ Long running operations, sometimes called async operations, tend to mean differe This section sets forth guidance around different types of long running operations, and describes the wire protocols and best practices for these types of operations. 1. One or more clients MUST be able to monitor and operate on the same resource at the same time. -2. The state of the system SHOULD be discoverable and testable at all times. Clients SHOULD be able to determine the system state even if the operation tracking resource is no longer active. The act of querying the state of a long running operation should itself leverage principles of the web. i.e. well defined resources with uniform interface semantics. Clients MAY issue a GET on some resource to determine the state of a long running operation +2. The state of the system SHOULD be discoverable and testable at all times. Clients SHOULD be able to determine the system state even if the operation tracking resource is no longer active. The act of querying the state of a long running operation should itself leverage principles of the web. i.e. well-defined resources with uniform interface semantics. Clients MAY issue a GET on some resource to determine the state of a long running operation 3. Long running operations SHOULD work for clients looking to "Fire and Forget" and for clients looking to actively monitor and act upon results. 4. Cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a cancelled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. @@ -1350,7 +1350,7 @@ An example may be a machine reboot, where the operation itself completes synchro This model MAY integrate Push Notifications. -While most operations are likely to be POST semantics, In addition to POST semantics services MAY support PUT semantics via routing to simplify their APIs. +While most operations are likely to be POST semantics, in addition to POST semantics, services MAY support PUT semantics via routing to simplify their APIs. For example, a user that wants to create a database named "db1" could call: ```http @@ -1375,7 +1375,7 @@ Services MUST perform as much synchronous validation as practical on stepwise re Services MUST prioritize returning errors in a synchronous way, with the goal of having only "Valid" operations processed using the long running operation wire protocol. For an API that's defined as a Stepwise Long Running Operation the service MUST go through the Stepwise Long Running Operation flow even if the operation can be completed immediately. -In other words, APIs must adopt and stick with a LRO pattern and not change patterns based on circumstance. +In other words, APIs must adopt and stick with an LRO pattern and not change patterns based on circumstance. #### 13.2.1. PUT Services MAY enable PUT requests for entity creation. @@ -1475,7 +1475,7 @@ If supported DELETE operations MUST be idempotent. > Note: From an API design perspective, cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. -Following a cancelled operation It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. +Following a cancelled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. Services that do not support operation cancellation MUST return a 405 Method Not Allowed in the event of a DELETE. @@ -1486,7 +1486,7 @@ Operations MUST support the following states: 3. Succeeded. Terminal State. 4. Failed. Terminal State. -Services MAY add additional states, such as "Cancelled" or "Partially Completed". Services that support cancellation MUST sufficiently describe their cancellation such that the state of the system can be accurately determined and any compensating actions may be run. +Services MAY add additional states, such as "Cancelled" or "Partially Completed". Services that support cancellation MUST sufficiently describe their cancellation such that the state of the system can be accurately determined, and any compensating actions may be run. Services that support additional states should consider this list of canonical names and avoid creating new names if possible: Cancelling, Cancelled, Aborting, Aborted, Tombstone, Deleting, Deleted. @@ -1719,7 +1719,7 @@ When a caller has made too many calls 8. Services MAY return other service specific RateLimit headers as appropriate for more detailed information or specific limits or quotas #### 14.4.3. Overloaded services -When a services are generally overloaded and load shedding +When services are generally overloaded and load shedding 1. Services MUST Return a 503 code 2. Services MUST Return a standard error response (see 7.10.2) describing the specifics so that a programmer can make appropriate changes @@ -1753,7 +1753,7 @@ RateLimit-Reset: 1538152773 Callers include all users of the API: tools, portals, other services, not just user clients 1. Callers MUST wait for a minimum of time indicated in a response with a Retry-After before retrying a request. -2. Callers MAY assume that request is retryable after receiving a response with a Retry-After header without making any changes to the request. +2. Callers MAY assume that request is retriable after receiving a response with a Retry-After header without making any changes to the request. 3. Clients SHOULD use shared SDKs and common transient fault libraries to implement the proper behavior See: https://docs.microsoft.com/en-us/azure/architecture/best-practices/transient-faults @@ -2199,7 +2199,7 @@ Services SHOULD NOT use the following names: - Services MUST name counts of resources with a noun or noun phrase suffixed with 'Count'. ### 17.9. Common property names -Where services have a property whose data matches the names below, the service MUST use the name from this table. +Where services have a property, whose data matches the names below, the service MUST use the name from this table. This table will grow as services add terms that will be more commonly used. Service owners adding such terms SHOULD propose additions to this document. From 6ca391b78f8bc8df27fa332cf7cd26eb93bbc8e9 Mon Sep 17 00:00:00 2001 From: Isara Naranirattisai Date: Sat, 12 Jan 2019 05:41:37 +0700 Subject: [PATCH 012/729] Add License badge (#159) Add "Creative Commons Attribution 4.0 International License" on README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 07ddd3fb..54c2cdc5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Microsoft REST API Guidelines +Creative Commons License
+ The [Microsoft REST API Guidelines](Guidelines.md), as a design principle, encourages application developers to have resources accessible to them via a RESTful HTTP interface. To provide the smoothest possible experience for developers on platforms following the Microsoft REST API Guidelines, REST APIs SHOULD follow [consistent design guidelines](Guidelines.md#7-consistency-fundamentals) to make using them easy and intuitive. ## Code of Conduct From 36224ba86f6a85c11ac5ae8e0a19830b2ff58bb1 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 11 Jan 2019 14:44:16 -0800 Subject: [PATCH 013/729] Revert "Add License badge (#159)" (#170) This reverts commit 6ca391b78f8bc8df27fa332cf7cd26eb93bbc8e9. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 54c2cdc5..07ddd3fb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ # Microsoft REST API Guidelines -Creative Commons License
- The [Microsoft REST API Guidelines](Guidelines.md), as a design principle, encourages application developers to have resources accessible to them via a RESTful HTTP interface. To provide the smoothest possible experience for developers on platforms following the Microsoft REST API Guidelines, REST APIs SHOULD follow [consistent design guidelines](Guidelines.md#7-consistency-fundamentals) to make using them easy and intuitive. ## Code of Conduct From 189047d333acd9aa6e65bac3446502a36cd69e7e Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Thu, 11 Apr 2019 14:40:33 -0700 Subject: [PATCH 014/729] Changed readme to state purpose of repo. Updated readme to state repo nature and purpose rather than having a sort of minimal summary of what a guideline is. --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 07ddd3fb..03837a3a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Microsoft REST API Guidelines -The [Microsoft REST API Guidelines](Guidelines.md), as a design principle, encourages application developers to have resources accessible to them via a RESTful HTTP interface. To provide the smoothest possible experience for developers on platforms following the Microsoft REST API Guidelines, REST APIs SHOULD follow [consistent design guidelines](Guidelines.md#7-consistency-fundamentals) to make using them easy and intuitive. +The [Microsoft REST API Guidelines](Guidelines.md) are Microsoft's internal company-wide REST API design guidelines. +Teams at Microsoft typically reference this document when setting API design policy. +They may additionally create documents specific to their team, adding further guidance or making adjustments as appropriate to their circumstances. + +We publish these guidelines here with the aim of fostering dialogue and learning in the API community at large. +We further hope that these guidelines may encourage other organizations to create guidelines that are appropriate for them and in turn, if they are able, to publish theirs. ## Code of Conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. From 628b2dcc604e8a6d9b94e13920b9e1a660f52ae7 Mon Sep 17 00:00:00 2001 From: "Johan Stenberg (MSFT)" Date: Thu, 11 Apr 2019 20:32:33 -0700 Subject: [PATCH 015/729] Polling on operation status to terminal state instead of 200 response (#172) --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index 4d49b2db..06024257 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -1541,7 +1541,7 @@ Services MAY choose to delete tombstones after a service defined period of time. #### 13.2.7. The typical flow, polling - Client invokes a stepwise operation by invoking an action using POST - The server MUST indicate the request has been started by responding with a 202 Accepted status code. The response SHOULD include the location header containing a URL that the client should poll for the results after waiting the number of seconds specified in the Retry-After header. -- Client polls the location until receiving a 200 OK response from the server. +- Client polls the location until receiving a 200 response with a terminal operation state. ##### Example of the typical flow, polling Client invokes the restart action: From fa7b1052fb13188d7a4cda6e6ab7f821ecb79808 Mon Sep 17 00:00:00 2001 From: "Johan Stenberg (MSFT)" Date: Thu, 11 Apr 2019 20:35:00 -0700 Subject: [PATCH 016/729] Provide note for 64bit integers (#174) * Provide note for 64bit integers * Address review feedback Update outdated reference to RFC-4627 to refer to RFC-825. Make recommendation to use string instead of number for large (and small) integers stronger. Fix typo. * Update Guidelines.md * Fixed typo --- Guidelines.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index 06024257..2a1fed81 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -1096,7 +1096,9 @@ If the delta link is no longer valid, the service MUST respond with _410 Gone_. ## 11. JSON standardizations ### 11.1. JSON formatting standardization for primitive types -Primitive values MUST be serialized to JSON following the rules of [RFC4627][rfc-4627]. +Primitive values MUST be serialized to JSON following the rules of [RFC8259][rfc-8259]. + +**Important note for 64bit integers:** JavaScript will silently truncate integers larger than `Number.MAX_SAFE_INTEGER` (2^53-1) or numbers smaller than `Number.MIN_SAFE_INTEGER` (-2^53+1). If the service is expected to return integer values outside the range of safe values, strongly consider returning the value as a string in order to maximize interoperability adn avoid data loss. ### 11.2. Guidelines for dates and times #### 11.2.1. Producing dates From 21106db06fefc734b5827008bada6b4ff6aa6bb8 Mon Sep 17 00:00:00 2001 From: Andrew Joshua Loria Date: Thu, 11 Apr 2019 20:48:52 -0700 Subject: [PATCH 017/729] Update CONTRIBUTING.md (#147) Added an anchor link to the "Recommended setup for contributing" section for better accessibility. --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 406acbed..33a882ea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,7 @@ To provide feedback, please follow the guidance in this document. Please note th Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. - [Creating issues](#creating-issues) +- [Recommended setup for contributing](#recommended-setup-for-contributing) - [Documentation styleguide](#documentation-styleguide) - [Commit messages](#commit-messages) - [Pull requests](#pull-requests) From c98f7a3098c22a833a57478ef6bef10ae76d1ba7 Mon Sep 17 00:00:00 2001 From: Jacob Herrington Date: Thu, 11 Apr 2019 22:52:07 -0500 Subject: [PATCH 018/729] Correct the spelling of predate (#139) From ac574eadfb688de49563e43c37be1a3093a487bc Mon Sep 17 00:00:00 2001 From: Daredevil Geek Date: Fri, 12 Apr 2019 15:55:05 +1200 Subject: [PATCH 019/729] Reworded compliance text to fix grammar (#140) --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index 2a1fed81..331c0e20 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -210,7 +210,7 @@ Obviously, a REST service that implements or must interoperate with some externa Some services MAY also have special performance needs that require a different format, such as a binary protocol. ### 4.2. Guidelines for existing services and versioning of services -We do not recommend making a breaking change to a service that predates these guidelines simply for compliance sake. +We do not recommend making a breaking change to a service that predates these guidelines simply for the sake of compliance. The service SHOULD try to become compliant at the next version release when compatibility is being broken anyway. When a service adds a new API, that API SHOULD be consistent with the other APIs of the same version. So if a service was written against version 1.0 of the guidelines, new APIs added incrementally to the service SHOULD also follow version 1.0. The service can then upgrade to align with the latest version of the guidelines at the service's next major release. From 6aae60420aa0e7d223126827fc3ebd0257d512df Mon Sep 17 00:00:00 2001 From: Chip Thien Date: Thu, 11 Apr 2019 22:56:35 -0500 Subject: [PATCH 020/729] Update Guidelines.md (#141) --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index 331c0e20..6ba198a4 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -251,7 +251,7 @@ For long running calls, the latency is measured on the initial request and measu Services that expose long operations MUST track "Time to Complete" metrics around those operations. ### 5.5. Long running API faults -For a Long Running API, it's possible for both the initial request to begin the operation and the request to retrieve the results to technically work (each passing back a 200), but for the underlying operation to have failed. +For a Long Running API, it's possible for both the initial request which begins the operation and the request which retrieves the results to technically work (each passing back a 200) but for the underlying operation to have failed. Long Running faults MUST roll up as faults into the overall Availability metrics. ## 6. Client guidance From 7a78e3afaaad6e82296fff92ec7e3aa21cccf692 Mon Sep 17 00:00:00 2001 From: Ian Jirka Date: Mon, 23 Sep 2019 13:49:55 -0700 Subject: [PATCH 021/729] Fix typo in "Important note for 64bit integers" (#180) --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index 6ba198a4..99cadbb3 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -1098,7 +1098,7 @@ If the delta link is no longer valid, the service MUST respond with _410 Gone_. ### 11.1. JSON formatting standardization for primitive types Primitive values MUST be serialized to JSON following the rules of [RFC8259][rfc-8259]. -**Important note for 64bit integers:** JavaScript will silently truncate integers larger than `Number.MAX_SAFE_INTEGER` (2^53-1) or numbers smaller than `Number.MIN_SAFE_INTEGER` (-2^53+1). If the service is expected to return integer values outside the range of safe values, strongly consider returning the value as a string in order to maximize interoperability adn avoid data loss. +**Important note for 64bit integers:** JavaScript will silently truncate integers larger than `Number.MAX_SAFE_INTEGER` (2^53-1) or numbers smaller than `Number.MIN_SAFE_INTEGER` (-2^53+1). If the service is expected to return integer values outside the range of safe values, strongly consider returning the value as a string in order to maximize interoperability and avoid data loss. ### 11.2. Guidelines for dates and times #### 11.2.1. Producing dates From 04b62ca31b944a3305703aadfbebebb7fabfbb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vit=C3=B3ria=20Heliane?= Date: Mon, 23 Sep 2019 17:55:08 -0300 Subject: [PATCH 022/729] Add link to redirect to the WebSequenceDiagrams web site (#144) * Add link to WebSequenceDiagramscom * Remove unnecessary text --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index 99cadbb3..c6b6418b 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -2244,7 +2244,7 @@ Service owners adding such terms SHOULD propose additions to this document. ## 18. Appendix ### 18.1. Sequence diagram notes -All sequence diagrams in this document are generated using the WebSequenceDiagrams.com web site. To generate them, paste the text below into the web tool. +All sequence diagrams in this document are generated using the [WebSequenceDiagrams.com](https://www.websequencediagrams.com/). To generate them, paste the text below into the web tool. #### 18.1.1. Push notifications, per user flow From 5f8e88139e82a541a2a40cd10dec9561e92dcacb Mon Sep 17 00:00:00 2001 From: juliegoat Date: Mon, 23 Sep 2019 17:00:33 -0400 Subject: [PATCH 023/729] Remove implication that code is an enumerated string in Response Types section (#125) --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index c6b6418b..de44dc18 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -550,7 +550,7 @@ Property | Type | Required | Description Property | Type | Required | Description -------- | ---- | -------- | ----------- -`code` | String (enumerated) | ✔ | One of a server-defined set of error codes. +`code` | String | ✔ | One of a server-defined set of error codes. `message` | String | ✔ | A human-readable representation of the error. `target` | String | | The target of the error. `details` | Error[] | | An array of details about specific errors that led to this reported error. From 3bf0a41dae1f3d36a589436a0d54f5e6bedabcad Mon Sep 17 00:00:00 2001 From: Matt Frear Date: Tue, 3 Dec 2019 11:26:42 +1300 Subject: [PATCH 024/729] Update Guidelines.md (#188) Remove unnecessary "54". --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index de44dc18..f9587c52 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -898,7 +898,7 @@ Example: all products that either have the name 'Milk' or have a price less than GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' or price lt 2.55 ``` -Example 54: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: +Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: ```http GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 From b998c0b0693fa76b40e1160cb422e00638ec927c Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 30 Mar 2020 15:50:16 -0700 Subject: [PATCH 025/729] Added license file. --- license.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 license.txt diff --git a/license.txt b/license.txt new file mode 100644 index 00000000..76dbc95a --- /dev/null +++ b/license.txt @@ -0,0 +1,2 @@ +This work is licensed under the Creative Commons Attribution 4.0 International License. +To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. From c3033b07387b59b5abdc601ff13e8315fd9e4d5b Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 31 Mar 2020 10:43:44 -0700 Subject: [PATCH 026/729] Added v3.0 of the Azure REST API Guidelines --- azure/Guidelines.md | 310 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 azure/Guidelines.md diff --git a/azure/Guidelines.md b/azure/Guidelines.md new file mode 100644 index 00000000..c1223cc1 --- /dev/null +++ b/azure/Guidelines.md @@ -0,0 +1,310 @@ +# Microsoft Azure REST Design Guidelines + +## History + +
+
2020-Mar-31 v3.0
+
Version 3.0 of the Azure REST API Guidelines was copied from Sharepoint +into Markdown and uploaded to GitHub as the basis for future improvements.
+
+ +## Introduction + +The Azure REST API guidelines are an extension of the Microsoft-wide [OneAPI guidelines][1] (which historically drew heavily from an earlier version of the Azure REST API guidelines). Readers of this document are assumed to be also reading the [OneAPI guidelines][1] and be familiar with them. Azure guidance is a superset of OneAPI guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. + +The OneAPI guidelines are available on Github at [https://github.com/microsoft/api-guidelines][1]. + +Highlights of the OneAPI guidelines include: + +* **REST fundamentals**. Guidance around REST basics such as HTTP headers, verbs, status codes, and other similar areas. +* **Versioning**. The guidance for versioning covers the externally facing mechanics of versioning and compatibility guarantees between versions. Guidance for when API owners should increment versions is also present. +* **CORS**. Guidance around the use of CORS. +* **Authentication**. Guidance around the authentication and its use. +* **Encoding**. Guidance around the use of JSON encoding and standards. +* **Error responses**. A standard format for error responses is closed. +* **Dates and times**. Detailed guidance around date and time formats and encoding is provided. +* **Relationship to OData**. Guidance around the relationship between OData and REST APIs. +* **JSON representation**. JSON is preferred over XML and other formats. +* **Long running operations**. The OneAPI guidelines are updated and simplified over the previous Azure guidelines. +* **Push notifications via Webhooks**. + +### Updating from previous versions of the Azure API guidelines + +The OneAPI guidelines started from the last revision of the Azure API Guidelines and are highly compatible. Azure services that are already using an earlier version of the guidelines should not require much additional work. + +#### Asynchronous operations + +The OneAPI guidelines for Long Running Operations guidelines are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the board decided to rename the HEADER used in the operation from `Azure-AsyncOperation` to `Operation-Location`. Services can and **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. + +### Additional guidance for Azure Resource Manager resource providers + +Teams building ARM RPs MUST follow the additional guidance in the ARM RPC and related documents. These documents can be found here. + +* [Azure Resource Manager Documents][2] (Internal only) +* [Azure Resource Provider Contract][3] (Internal only) + +ARM RPs are a CEC requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. + +## Swagger to describe API + +All Services **MUST** provide Swagger that describes their service. Swagger is a key element of the Azure SDK plan and essential to improving the usability and discoverability of services. Swagger is a CEC requirement for Azure Services + +## URL structure + +In addition to the URL structure guidance in the OneAPI guidelines, Azure has specific guidance about service exposure for multi-tenant services + +### URL structure + +All services **MUST** expose their service to developers via the following URL pattern: + +``` +http(s)://.azure.net// +``` + +Where: + +* **service** - the name of the service such as "blobstore", "servicebus", "directory", or "management" +* **service-defined-root** - the root of the service-specific path, such as "blobcontainer", "myqueue", etc. +* **unit-of-multi-tenancy** - refers to a globally unique moniker that identifies a unique container in the Azure service that has the following properties: + + * This container is the boundary of isolation between different tenants of the service. + * Quotas as set and enforced at the level of this container - but there will be different limits for different operations; and operations will be service specific. + * Resources in the service are attached to this container and are tied to this container in terms of lifecycle. For example someone signs up, they get this container. If they unsubscribe (or don’t pay their bills) then cleanup of this container occurs and the resources associated with this container are cleaned up. Cleanup follows a state machine – the container and the resources attached to it are deactivated first (and can be easily restored if required), and if no response for some period then deleted. + * It is the container for billing – which means the owner of this container sees one bill for the resource usage of all azure services under this container’s identifier. + +For Azure PaaS services like SQL Azure, Azure Storage, Caching, etc., the unit of multi-tenancy is the Azure subscription id, which is a GUID. This ensures consistent access using the same URL pattern, and identifier, across all these services. + +When services produce URLs in response headers or bodies, they **MUST** use a consistent form – either always a GUID for tenant identifier or always a single verified domain - regardless of the URL used to reach the resource + +### Direct endpoint URLs + +In addition to the required format above, services **MAY** also choose to expose direct endpoint for performance or routing reasons. The direct endpoint should be discoverable by clients, to ensure that developers are presented with a consistent pattern for accessing Azure services. + +The format of the root of the direct endpoint **MUST** be as follows: + +``` +http(s)://-..azure.net +``` + +1. A request is made to the default end point (GET or HEAD). For example: + + ``` + GET https://blobstore.azure.net/contoso.com/account1/container1/blob2 + ``` + +2. That request is returned with the `Content-Location` header set to the direct endpoint. See [RFC2557]: + + ``` + 200 OK + Content-Location: http://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 + ``` + + Or, with the GUID format: + + ``` + 200 OK + Content-Location: http://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 + ``` + +## Versioning + +All Azure APIs **MUST** support explicit versioning. It's critical that clients can count on services to be stable over time, and it's critical that Azure services can add features and make changes. + +The OneAPI guidelines offer a couple of different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes which options are required of Azure services as well as some guidance about deprecation policy. There is also a section about additional versioning practices necessary to support Azure Stack and Azure compatibility. + +Further discussion on versioning principles and criteria can be found in the [Azure API versioning guidelines][4] (Internal only). + +### Specifying the version in Azure + +The OneAPI guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: + +``` +GET http://blobstore.azure.com/foo.com/acct1/c1/blob2?api-version=1.0 +PUT http://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 +POST http://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 +``` + +### Breaking changes in Azure + +A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). + +Even though we recommend clients ignore new fields, there are many libraries and clients that are strict. Therefore, Azure services **MUST** update the version number of their API even when adding optional fields. In fact, servers should be as strict as possible. Ignoring a field can result in the API accepting content that containered a typo or an element at the wrong level of nesting. If this missing field changes the semantics (for example, we have seen cases where security settings were misplaced and ignored, leaving the resources more exposed than intended) this can be a huge and hard to discover error. + +### Group versioning in Azure and Azure Stack + +Azure Stack allows customers and hosters to deploy their own small versions of Azure and upgrade it at a different pace than Azure. In order to make it possible to write an application or SDK that targets both Azure and Azure Stack, additional versioning policy is necessary. This guidance is still being worked on, but the latest can be found here: [Azure and Azure Stack Versioning][5]. + +### Version discovery + +Simpler clients may be hardcoded to a single version of a service. Since Azure services offer each version for a well-known period of time, a client that’s regularly maintained can be always operational without further complexity as long as during regular maintenance the client is moved forward to new versions in advance of older ones being retired. + +API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. + +Azure services **SHOULD** support API version discovery. If they support it: + +1. Services **MUST** support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service +2. Services **MUST** include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. +3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it must include those versions (group and major.minor) in the `api-deprecated-versions` header. +4. In addition to the functionality described here, services may support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. +5. Services may allow unauthenticated HTTP `OPTIONS` requests. When doing so authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. +6. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends should report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. +7. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request should return the group versions that are supported across the expanded set. + +Example request to discover versions (blob storage container list API): + +``` +OPTIONS /?comp=list HTTP/1.1 +host: accountname.blob.core.azure.net +``` + +Example response: + +``` +200 OK +api-supported-versions: 2011-08,2012-02,1.1,2.0 +api-deprecated-versions: 2009-04,1.0 +Content-Length: 0 +``` + +Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. + +## API deprecation policy + +Disabling a runtime REST API that customers are dependent on of course has the potential of breaking their applications or services, perhaps even mission critical services. But inevitably our APIs will become obsolete and the cost of supporting them and operating the servers on which they run will require us to deprecate and shut them down. We have a public policy that describes how we will inform customers that deprecation is coming and help them move their applications off these services and on to their replacements. + +### Policy + +Azure does not have a single SLA for how long we will support all services. However, we have published expectations such as [here][6]. The most relevant section of the document: + +> Azure Cloud Services will support no fewer than the latest two SDK versions for deploying new Cloud Services. Microsoft will provide notification 12 months before retiring a SDK in order to smooth the transition to a supported version. + +In practice, we have found this is a bare minimum of how long service endpoints must be supported. Services with any significant usage **SHOULD** expect to run until customers are no longer using them, which can be 10 years or more. + +#### Special case for pre-release and beta APIs + +Pre-release and beta APIs are not covered by the normal API deprecation policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. + +Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy and process. ** *Customers have suffered downtime because of deprecation of preview APIs* **. In some cases the code was written by consultants or employees without any awareness on behalf of the customer. + +## Process + +This section describes the process of implementing this policy. This process should: + +* Minimize surprise. Customers, internal partners, management, the SDK and documentation team. All should be surprised as little as possible by the deprecation of an API. +* Be lightweight and simple to implement + +The key to minimizing surprise is to communicate the decision to deprecate an API as early and as widely as possible. Because the policy guarantees a minimum of 12 months between notification and actual retirement, there should be plenty of time to communicate and adjust plans based on feedback or changing conditions. + +Basically the process should be an iteration of “make a decision to proceed”; “communicate the decision”, “evaluate the feedback” and “iterate” on whether to move to the next step of the process or to change the decision or the timeframe. This is a flexible process, but the suggested steps are: + +### Step 1: Make a business decision to deprecate the API + +The decision to deprecate a REST API (or tool or client library etc.) is a business decision and should be made by the responsible business unit based on factors such as usage data, the degree to which the service is mission critical, potential for data loss or customer downtown, and the costs of continuing to support and operate the service. In the spirit of the rest of this process, the decision to deprecate a REST API should come as little surprise: it should be obvious by that point that there is a newer version of the service that is widely available and offers advantages over the version being deprecated, and plenty of time should have passed for customers to update to the new version (at least by the end of the 12 month retirement grace window described by policy). There is no requirement of executive approval for API deprecation, but in the spirit of reducing surprise, communication with management is encouraged. + +### Step 2: Document the decision + +As decision is being made, it should be documented. The documentation should answer such questions as: + +* What versions of which APIs are being deprecated? +* What versions of which APIs replace those being deprecated? +* Who is impacted by deprecating these APIs? Which internal partners? What customers? +* Why are the APIs being deprecated? Support cost? COGS? Security or other reasons? +* When exactly will the APIs be deprecated? + +### Step 3: Communicate with major partners and customers + +Many services will have particularly important customers or partners dependent on the versions of the APIs being deprecated. The team should make an effort to contact these early in the process and get their feedback. Ideally the feedback from these contacts can still be used to reevaluate the decision to deprecate an API or evaluate the appropriateness of the replacement APIs. + +### Step 4: Communicate to the Azure REST API Oversight and Azure SDK stakeholders + +The Azure CEC REST API review board, Azure SDK and documentation teams should be informed via e-mail of the decision. There should be a short window of time (2 work days) for these teams to provide feedback. Silence should be interpreted as assent to move to the next step. These teams may suggest coordinating multiple deprecating APIs from multiple teams in order to streamline the release and communication of changes. + +### Step 5: Communicate widely internally + +The decision should be communicated widely within Azure. Mail describing the decision should be sent to the Azure API review board, WW – Communities and other appropriate aliases. There should be another short (2-5 working days) feedback period before moving on to the next step. + +### Step 6: Communicate externally, starting the 12 month clock + +The decision next **MUST** be communicated to customers and partners. The Service Notices site gives instructions for communicating this and other service changes. The Playbook contains a link to a submission form to request e-mail be sent. + +https://microsoft.sharepoint.com/teams/azurecomms/SitePages/Email-Request.aspx + +Detail: + +> **Sunsetting/retirement**: These emails are sent when a service or feature is going to be deprecated/retired. For services or features that are generally available, we must provide at least 1 years’ notice, and then we send regular reminders to the impacted customers to remind them that the service is being retired (typical cadence is: 1 year prior to end date, reminders: 6 months prior, 3 months prior, 6 weeks, and then weekly, depending on number of customers who are still impacted). For services or features in preview, stakeholders should consider the customer impact, how long it will take to migrate customers to another service or feature, and if there is feature parity with the new service or feature. In addition, stakeholders should get LCA review/approval of their sunsetting/retirement communication strategy. + +It is important to remember that these notifications need to be localized. + +### Step 7: Update the SDKs and documentation + +#### REST API documentation + +The supported REST API group version(s) will be indicated on the [REST API reference landing page for the particular service][8] with a table similar to below: + +| REST API | Notes | +| **XXX Service** | | +| 2011-01-23+ | Supported | +| 2010-11-01 | Deprecated - April 23, 2013 | + +Alternatively, customers can go to the reference for a [specific API][9] and see whether the particular API is in the 12-month deprecation window. Once the API has been removed, the reference documentation will be removed, as well. + +#### Client libraries + +NuGet packages become deprecated no later than when the corresponding REST APIs become deprecated. This will be indicated in the following ways: + +1. **On the NuGet gallery**: A deprecation note and date will be added to the description for the NuGet package. +2. **On azure.com**: A table (grouped by programming language) similar to below will be updated: + +| Client Library | REST Version | Note | +| **XXX Client** || +| 1.7 | 2011-01-23 || +| 1.6 | 2010-11-01 | Deprecated - April 23, 2013 | + +This package will be also linked to from other relevant documentation pages on azure.com, MSDN, etc. + +#### Tools and "All in one" SDK bundles + +Tools become deprecated no later than when *any* of the corresponding REST APIs the tools depend on also become deprecated. This will be indicated on azure.com with a table similar to below: + +| Tools | Note | +| **Azure SDK for .NET (with VS Tooling)** || +| 2.0+ | Supported | +| October 2012 | Deprecated - April 23, 2013 | +||| +| **Azure PowerShell ** || +| 0.6.12+ | Supported | +| 0.6.11 | Deprecated - April 23, 2013 | + +This page will be linked to from other relevant documentation pages. + +### Step 8: Start reporting deprecated version of the API to discovery APIs + +Section 9.5 of the REST API guidelines describes a mechanism for discovering supported versions of APIs. It includes a mechanism for reporting deprectated APIs: + +* If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it **MUST** include those versions (group and major.minor) in the `api-deprecated-versions` header. + +At this point in the process, services should start using this mechanism. + +### Step 9: Continue to monitor the REST API endpoints during the retirement period + +During the 12 month retirement window, the team should continue to monitor the use of the REST API and take feedback from customers. If conditions change or new feedback brings the original decision or timing into question, the team should be prepared to cancel or postpone deprecation. If the decision is changed, the same Steps 1 through 7 above should be followed to communicate the updated decision. + +### Step 10: Turn off the REST endpoint + +After the 12 month retirement grace period has passed, the team can turn off their REST API during the normal process of updating their services. + + +[1]: https://github.com/microsoft/api-guidelines +[2]: http://sharepoint/sites/AzureUX/Sparta/Shared%20Documents/Forms/AllItems.aspx?RootFolder=%2fsites%2fAzureUX%2fSparta%2fShared%20Documents%2fSpecs&FolderCTID=0x012000BE9C61C2BE4C444E8018AB8C82DE4CF9 +[3]: http://sharepoint/sites/AzureUX/Sparta/Shared%20Documents/Specs/Resource%20Provider%20API%20v2.docx?d=wcc855351793a4417a536ab992218ad55 +[4]: https://microsoft.sharepoint.com/teams/azure-arc/SitePages/API%20Versioning.aspx +[5]: https://microsoft.sharepoint.com/teams/AzureStack/_layouts/15/WopiFrame.aspx?sourcedoc=%7bD5665517-0CDF-4E9C-9B9A-D053140E3FA7%7d&file=Azure%20and%20Azure%20Stack%20API%20Versioning.docx +[6]: http://support.microsoft.com/gp/azure-cloud-lifecycle-faq + +[7]: https://microsoft.sharepoint.com/teams/azurecomms/SitePages/Email-Request.aspx +[8]: https://docs.microsoft.com/en-us/previous-versions/azure/ee460799(v=azure.100) +[9]: https://docs.microsoft.com/en-us/previous-versions/azure/reference/ee460813(v=azure.100)?redirectedfrom=MSDN + + +[RFC2557]: http://www.ietf.org/rfc/rfc2557.txt + From 8231bb4d3bae1c4612a4116b90ca252ea7836656 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 31 Mar 2020 11:29:26 -0700 Subject: [PATCH 027/729] Updates based on feedback from @johngossman --- azure/Guidelines.md | 138 +++++--------------------------------------- 1 file changed, 13 insertions(+), 125 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index c1223cc1..63f1324f 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -28,10 +28,6 @@ Highlights of the OneAPI guidelines include: * **Long running operations**. The OneAPI guidelines are updated and simplified over the previous Azure guidelines. * **Push notifications via Webhooks**. -### Updating from previous versions of the Azure API guidelines - -The OneAPI guidelines started from the last revision of the Azure API Guidelines and are highly compatible. Azure services that are already using an earlier version of the guidelines should not require much additional work. - #### Asynchronous operations The OneAPI guidelines for Long Running Operations guidelines are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the board decided to rename the HEADER used in the operation from `Azure-AsyncOperation` to `Operation-Location`. Services can and **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. @@ -40,6 +36,8 @@ The OneAPI guidelines for Long Running Operations guidelines are an updated, cla Teams building ARM RPs MUST follow the additional guidance in the ARM RPC and related documents. These documents can be found here. + + * [Azure Resource Manager Documents][2] (Internal only) * [Azure Resource Provider Contract][3] (Internal only) @@ -112,8 +110,6 @@ All Azure APIs **MUST** support explicit versioning. It's critical that clients The OneAPI guidelines offer a couple of different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes which options are required of Azure services as well as some guidance about deprecation policy. There is also a section about additional versioning practices necessary to support Azure Stack and Azure compatibility. -Further discussion on versioning principles and criteria can be found in the [Azure API versioning guidelines][4] (Internal only). - ### Specifying the version in Azure The OneAPI guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: @@ -132,7 +128,7 @@ Even though we recommend clients ignore new fields, there are many libraries and ### Group versioning in Azure and Azure Stack -Azure Stack allows customers and hosters to deploy their own small versions of Azure and upgrade it at a different pace than Azure. In order to make it possible to write an application or SDK that targets both Azure and Azure Stack, additional versioning policy is necessary. This guidance is still being worked on, but the latest can be found here: [Azure and Azure Stack Versioning][5]. +Azure Stack allows customers and hosters to deploy their own small versions of Azure and upgrade it at a different pace than Azure. In order to make it possible to write an application or SDK that targets both Azure and Azure Stack, additional versioning policy is necessary. Contact the Azure Stack team for further guidance. ### Version discovery @@ -174,137 +170,29 @@ Disabling a runtime REST API that customers are dependent on of course has the p ### Policy -Azure does not have a single SLA for how long we will support all services. However, we have published expectations such as [here][6]. The most relevant section of the document: +Azure does not have a single SLA for how long we will support all services. However, we have published expectations such as [here][6]. The most relevant section of the document: > Azure Cloud Services will support no fewer than the latest two SDK versions for deploying new Cloud Services. Microsoft will provide notification 12 months before retiring a SDK in order to smooth the transition to a supported version. In practice, we have found this is a bare minimum of how long service endpoints must be supported. Services with any significant usage **SHOULD** expect to run until customers are no longer using them, which can be 10 years or more. +Service teams **MUST** contact the Azure API review board before communicating the deprecation externally to customers and partners (which starts the 12 month clock). + +Refer to the Azure deprecation policy for more details. + #### Special case for pre-release and beta APIs Pre-release and beta APIs are not covered by the normal API deprecation policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy and process. ** *Customers have suffered downtime because of deprecation of preview APIs* **. In some cases the code was written by consultants or employees without any awareness on behalf of the customer. -## Process - -This section describes the process of implementing this policy. This process should: - -* Minimize surprise. Customers, internal partners, management, the SDK and documentation team. All should be surprised as little as possible by the deprecation of an API. -* Be lightweight and simple to implement - -The key to minimizing surprise is to communicate the decision to deprecate an API as early and as widely as possible. Because the policy guarantees a minimum of 12 months between notification and actual retirement, there should be plenty of time to communicate and adjust plans based on feedback or changing conditions. - -Basically the process should be an iteration of “make a decision to proceed”; “communicate the decision”, “evaluate the feedback” and “iterate” on whether to move to the next step of the process or to change the decision or the timeframe. This is a flexible process, but the suggested steps are: - -### Step 1: Make a business decision to deprecate the API - -The decision to deprecate a REST API (or tool or client library etc.) is a business decision and should be made by the responsible business unit based on factors such as usage data, the degree to which the service is mission critical, potential for data loss or customer downtown, and the costs of continuing to support and operate the service. In the spirit of the rest of this process, the decision to deprecate a REST API should come as little surprise: it should be obvious by that point that there is a newer version of the service that is widely available and offers advantages over the version being deprecated, and plenty of time should have passed for customers to update to the new version (at least by the end of the 12 month retirement grace window described by policy). There is no requirement of executive approval for API deprecation, but in the spirit of reducing surprise, communication with management is encouraged. - -### Step 2: Document the decision - -As decision is being made, it should be documented. The documentation should answer such questions as: - -* What versions of which APIs are being deprecated? -* What versions of which APIs replace those being deprecated? -* Who is impacted by deprecating these APIs? Which internal partners? What customers? -* Why are the APIs being deprecated? Support cost? COGS? Security or other reasons? -* When exactly will the APIs be deprecated? - -### Step 3: Communicate with major partners and customers - -Many services will have particularly important customers or partners dependent on the versions of the APIs being deprecated. The team should make an effort to contact these early in the process and get their feedback. Ideally the feedback from these contacts can still be used to reevaluate the decision to deprecate an API or evaluate the appropriateness of the replacement APIs. - -### Step 4: Communicate to the Azure REST API Oversight and Azure SDK stakeholders - -The Azure CEC REST API review board, Azure SDK and documentation teams should be informed via e-mail of the decision. There should be a short window of time (2 work days) for these teams to provide feedback. Silence should be interpreted as assent to move to the next step. These teams may suggest coordinating multiple deprecating APIs from multiple teams in order to streamline the release and communication of changes. - -### Step 5: Communicate widely internally - -The decision should be communicated widely within Azure. Mail describing the decision should be sent to the Azure API review board, WW – Communities and other appropriate aliases. There should be another short (2-5 working days) feedback period before moving on to the next step. - -### Step 6: Communicate externally, starting the 12 month clock - -The decision next **MUST** be communicated to customers and partners. The Service Notices site gives instructions for communicating this and other service changes. The Playbook contains a link to a submission form to request e-mail be sent. - -https://microsoft.sharepoint.com/teams/azurecomms/SitePages/Email-Request.aspx - -Detail: - -> **Sunsetting/retirement**: These emails are sent when a service or feature is going to be deprecated/retired. For services or features that are generally available, we must provide at least 1 years’ notice, and then we send regular reminders to the impacted customers to remind them that the service is being retired (typical cadence is: 1 year prior to end date, reminders: 6 months prior, 3 months prior, 6 weeks, and then weekly, depending on number of customers who are still impacted). For services or features in preview, stakeholders should consider the customer impact, how long it will take to migrate customers to another service or feature, and if there is feature parity with the new service or feature. In addition, stakeholders should get LCA review/approval of their sunsetting/retirement communication strategy. - -It is important to remember that these notifications need to be localized. - -### Step 7: Update the SDKs and documentation - -#### REST API documentation - -The supported REST API group version(s) will be indicated on the [REST API reference landing page for the particular service][8] with a table similar to below: - -| REST API | Notes | -| **XXX Service** | | -| 2011-01-23+ | Supported | -| 2010-11-01 | Deprecated - April 23, 2013 | - -Alternatively, customers can go to the reference for a [specific API][9] and see whether the particular API is in the 12-month deprecation window. Once the API has been removed, the reference documentation will be removed, as well. - -#### Client libraries - -NuGet packages become deprecated no later than when the corresponding REST APIs become deprecated. This will be indicated in the following ways: - -1. **On the NuGet gallery**: A deprecation note and date will be added to the description for the NuGet package. -2. **On azure.com**: A table (grouped by programming language) similar to below will be updated: - -| Client Library | REST Version | Note | -| **XXX Client** || -| 1.7 | 2011-01-23 || -| 1.6 | 2010-11-01 | Deprecated - April 23, 2013 | - -This package will be also linked to from other relevant documentation pages on azure.com, MSDN, etc. - -#### Tools and "All in one" SDK bundles - -Tools become deprecated no later than when *any* of the corresponding REST APIs the tools depend on also become deprecated. This will be indicated on azure.com with a table similar to below: - -| Tools | Note | -| **Azure SDK for .NET (with VS Tooling)** || -| 2.0+ | Supported | -| October 2012 | Deprecated - April 23, 2013 | -||| -| **Azure PowerShell ** || -| 0.6.12+ | Supported | -| 0.6.11 | Deprecated - April 23, 2013 | - -This page will be linked to from other relevant documentation pages. - -### Step 8: Start reporting deprecated version of the API to discovery APIs - -Section 9.5 of the REST API guidelines describes a mechanism for discovering supported versions of APIs. It includes a mechanism for reporting deprectated APIs: - -* If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it **MUST** include those versions (group and major.minor) in the `api-deprecated-versions` header. - -At this point in the process, services should start using this mechanism. - -### Step 9: Continue to monitor the REST API endpoints during the retirement period - -During the 12 month retirement window, the team should continue to monitor the use of the REST API and take feedback from customers. If conditions change or new feedback brings the original decision or timing into question, the team should be prepared to cancel or postpone deprecation. If the decision is changed, the same Steps 1 through 7 above should be followed to communicate the updated decision. - -### Step 10: Turn off the REST endpoint - -After the 12 month retirement grace period has passed, the team can turn off their REST API during the normal process of updating their services. - [1]: https://github.com/microsoft/api-guidelines +[RFC2557]: http://www.ietf.org/rfc/rfc2557.txt + + [2]: http://sharepoint/sites/AzureUX/Sparta/Shared%20Documents/Forms/AllItems.aspx?RootFolder=%2fsites%2fAzureUX%2fSparta%2fShared%20Documents%2fSpecs&FolderCTID=0x012000BE9C61C2BE4C444E8018AB8C82DE4CF9 [3]: http://sharepoint/sites/AzureUX/Sparta/Shared%20Documents/Specs/Resource%20Provider%20API%20v2.docx?d=wcc855351793a4417a536ab992218ad55 -[4]: https://microsoft.sharepoint.com/teams/azure-arc/SitePages/API%20Versioning.aspx -[5]: https://microsoft.sharepoint.com/teams/AzureStack/_layouts/15/WopiFrame.aspx?sourcedoc=%7bD5665517-0CDF-4E9C-9B9A-D053140E3FA7%7d&file=Azure%20and%20Azure%20Stack%20API%20Versioning.docx -[6]: http://support.microsoft.com/gp/azure-cloud-lifecycle-faq - -[7]: https://microsoft.sharepoint.com/teams/azurecomms/SitePages/Email-Request.aspx -[8]: https://docs.microsoft.com/en-us/previous-versions/azure/ee460799(v=azure.100) -[9]: https://docs.microsoft.com/en-us/previous-versions/azure/reference/ee460813(v=azure.100)?redirectedfrom=MSDN - - -[RFC2557]: http://www.ietf.org/rfc/rfc2557.txt + +[6]: http://support.microsoft.com/gp/azure-cloud-lifecycle-faq From f89d062965bcb25174baab9ad2d4be07c95a1058 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 31 Mar 2020 11:33:11 -0700 Subject: [PATCH 028/729] clarified the document title of the azure-cloud-lifecycle-faq --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 63f1324f..fcf5ce18 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -170,7 +170,7 @@ Disabling a runtime REST API that customers are dependent on of course has the p ### Policy -Azure does not have a single SLA for how long we will support all services. However, we have published expectations such as [here][6]. The most relevant section of the document: +Azure does not have a single SLA for how long we will support all services. However, we have published expectations such as [the Azure Cloud Lifecycle FAQ][6]. The most relevant section of the document: > Azure Cloud Services will support no fewer than the latest two SDK versions for deploying new Cloud Services. Microsoft will provide notification 12 months before retiring a SDK in order to smooth the transition to a supported version. From 81770383487e03ac42ebd657227482a4a349e423 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 31 Mar 2020 11:42:49 -0700 Subject: [PATCH 029/729] Updated links to ARM RPC --- azure/Guidelines.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index fcf5ce18..22c37b31 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -36,10 +36,8 @@ The OneAPI guidelines for Long Running Operations guidelines are an updated, cla Teams building ARM RPs MUST follow the additional guidance in the ARM RPC and related documents. These documents can be found here. - - -* [Azure Resource Manager Documents][2] (Internal only) -* [Azure Resource Provider Contract][3] (Internal only) +* [Azure Resource Manager Wiki][2] (Internal only) +* [Azure Resource Provider Contract][3] ARM RPs are a CEC requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. @@ -191,8 +189,8 @@ Though services may set their own deprecation policy for pre-release APIs, they [RFC2557]: http://www.ietf.org/rfc/rfc2557.txt -[2]: http://sharepoint/sites/AzureUX/Sparta/Shared%20Documents/Forms/AllItems.aspx?RootFolder=%2fsites%2fAzureUX%2fSparta%2fShared%20Documents%2fSpecs&FolderCTID=0x012000BE9C61C2BE4C444E8018AB8C82DE4CF9 -[3]: http://sharepoint/sites/AzureUX/Sparta/Shared%20Documents/Specs/Resource%20Provider%20API%20v2.docx?d=wcc855351793a4417a536ab992218ad55 +[2]: https://aka.ms/armwiki +[3]: https://github.com/Azure/azure-resource-manager-rpc [6]: http://support.microsoft.com/gp/azure-cloud-lifecycle-faq From 0aacf3be1131b2f64b6c422340f5a71b23b046eb Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 31 Mar 2020 12:00:35 -0700 Subject: [PATCH 030/729] Added breaking change updates --- azure/Guidelines.md | 69 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 22c37b31..2d6594c8 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -43,7 +43,7 @@ ARM RPs are a CEC requirement for Azure Services and ARM RP review is another ma ## Swagger to describe API -All Services **MUST** provide Swagger that describes their service. Swagger is a key element of the Azure SDK plan and essential to improving the usability and discoverability of services. Swagger is a CEC requirement for Azure Services +All Services **MUST** provide Swagger that describes their service. Swagger is a key element of the Azure SDK plan and essential to improving the usability and discoverability of services. Swagger is a CEC requirement for Azure Services. ## URL structure @@ -70,7 +70,7 @@ Where: For Azure PaaS services like SQL Azure, Azure Storage, Caching, etc., the unit of multi-tenancy is the Azure subscription id, which is a GUID. This ensures consistent access using the same URL pattern, and identifier, across all these services. -When services produce URLs in response headers or bodies, they **MUST** use a consistent form – either always a GUID for tenant identifier or always a single verified domain - regardless of the URL used to reach the resource +When services produce URLs in response headers or bodies, they **MUST** use a consistent form – either always a GUID for tenant identifier or always a single verified domain - regardless of the URL used to reach the resource. ### Direct endpoint URLs @@ -124,6 +124,69 @@ A breaking change is any change in the API that may cause client or service code Even though we recommend clients ignore new fields, there are many libraries and clients that are strict. Therefore, Azure services **MUST** update the version number of their API even when adding optional fields. In fact, servers should be as strict as possible. Ignoring a field can result in the API accepting content that containered a typo or an element at the wrong level of nesting. If this missing field changes the semantics (for example, we have seen cases where security settings were misplaced and ignored, leaving the resources more exposed than intended) this can be a huge and hard to discover error. +At a high level, changes to the contract of an API constitute a breaking change. Changes that impact backwards compatibility of an API is also considered a breaking change. Teams MAY define backwards compatibility as their business needs require. For example, Azure defines the addition of a new JSON field in a response to be not backwards compatible. Anything that would violate the Principle of Least Astonishment is considered a breaking change in Azure. Below are some concrete examples of what constitutes a breaking change. In the below breaking change scenarios, the API version must be changed. + +#### Existing property is removed + +If a property called `foo` that was present in v1 of the API needs to be removed, it must be done in a newer API version. + +#### New property added to response + +If a new property/field is added to the response an API, the GET-PUT pipeline will be broken. Consider the case where from portal a customer updates the value of a new property "A". Another customer does a GET of this resource using the SDK. The SDK will ignore the property since it does not understand it. From the SDK, the customer does a PUT using the model that was returned from the GET. This will overwrite the change made by the first customer from the portal. + +#### New required property added to request + +If a new property is made required in the request body, clients will have no way to set this and the request will fail. + +#### Property name has changed + +Note that this is implied by the requirement that adding/and removing properties are breaking changes, but in some ways worse, since it leads to the possibility of reusing a property name. Even with an API version change, this change is discouraged because it creates documentation and cognitive challenges. + +#### Property type has changed + +Property `foo was a boolean in v1 but is changed to a string. A client using the existing API version tries to set it as a boolean, but the service will fail since its now expecting a string. So, the API version must be updated. + +#### Property default value has changed + +If a property is optional and the service provides a default value, changing that default requires an updated API version. + +#### Allowed values for an enum have changed +Enum “foo” had allowed values as “val1” and “val2” in v1 of API. Now, the values accepted by the service are “val1”, “val2” and “val3”. The client will fail to de-serialize if “val3” comes back in the response. + +#### API has been removed or renamed + +V1 of API contract supported `PUT /resourceType1/{resourceType1_name}` but the service no longer supports this method. This scenario should follow the proper Azure API deprecation policy and must be done in an updated API version. + +#### Behavior of existing API has changed + +There is a functional change in what the API was doing. This is a complex issue because it sometimes is not an easy option to maintain the old behavior even on an old API version. It also is very confusing to end users even when version is update and documented. Behavior changes need to be well justified and discussed on a case-by-case basis. + +#### Error contracts have changed + +#### Property is made required (from optional) + +If property “foo” was optional in the request body of v1 and now it is required, this should result in an API version change. If not changed, clients relying on the older API version will fail if this property is not passed. + +#### URL format has changed + +Resource parameter names change from `/resourceType1/{resourceType1_name}` to `/resourceType1/{resourceType1_id}`. This will impact code generation. + +#### Resource naming rules should not change + +This could result in failures which would have earlier succeeded. Even if the rules become less strict, clients relying on earlier name constraints to perform local validation will fail. + +### Non-Breaking Changes + +The following changes are considered backwards compatible and hence non-breaking. + +#### Adding new APIs to an existing service + +When a new resource types is added, it does not require API version to be updated for existing types. + +#### Bug fixes to existing API + +Bug fixes to existing API which don’t fall into one of the above categories of breaking changes as described above are fine. + ### Group versioning in Azure and Azure Stack Azure Stack allows customers and hosters to deploy their own small versions of Azure and upgrade it at a different pace than Azure. In order to make it possible to write an application or SDK that targets both Azure and Azure Stack, additional versioning policy is necessary. Contact the Azure Stack team for further guidance. @@ -174,7 +237,7 @@ Azure does not have a single SLA for how long we will support all services. Howe In practice, we have found this is a bare minimum of how long service endpoints must be supported. Services with any significant usage **SHOULD** expect to run until customers are no longer using them, which can be 10 years or more. -Service teams **MUST** contact the Azure API review board before communicating the deprecation externally to customers and partners (which starts the 12 month clock). +Service teams **MUST** contact the Azure API review board before communicating the deprecation externally to customers and partners (which starts the 12 month clock). Refer to the Azure deprecation policy for more details. From ab075b22ca68f823bdf8b238fe5c03f34ab6c8be Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 31 Mar 2020 16:39:37 -0700 Subject: [PATCH 031/729] Updates based on review from @garethj-msft --- azure/Guidelines.md | 47 ++++++++++++++------------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 2d6594c8..1adbf1b8 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -2,39 +2,20 @@ ## History -
-
2020-Mar-31 v3.0
-
Version 3.0 of the Azure REST API Guidelines was copied from Sharepoint -into Markdown and uploaded to GitHub as the basis for future improvements.
-
+| Date | Version | Notes | +| 2020-Mar-31 | v3.1 | Merge of Azure guidelines with breaking changes update | ## Introduction -The Azure REST API guidelines are an extension of the Microsoft-wide [OneAPI guidelines][1] (which historically drew heavily from an earlier version of the Azure REST API guidelines). Readers of this document are assumed to be also reading the [OneAPI guidelines][1] and be familiar with them. Azure guidance is a superset of OneAPI guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. - -The OneAPI guidelines are available on Github at [https://github.com/microsoft/api-guidelines][1]. - -Highlights of the OneAPI guidelines include: - -* **REST fundamentals**. Guidance around REST basics such as HTTP headers, verbs, status codes, and other similar areas. -* **Versioning**. The guidance for versioning covers the externally facing mechanics of versioning and compatibility guarantees between versions. Guidance for when API owners should increment versions is also present. -* **CORS**. Guidance around the use of CORS. -* **Authentication**. Guidance around the authentication and its use. -* **Encoding**. Guidance around the use of JSON encoding and standards. -* **Error responses**. A standard format for error responses is closed. -* **Dates and times**. Detailed guidance around date and time formats and encoding is provided. -* **Relationship to OData**. Guidance around the relationship between OData and REST APIs. -* **JSON representation**. JSON is preferred over XML and other formats. -* **Long running operations**. The OneAPI guidelines are updated and simplified over the previous Azure guidelines. -* **Push notifications via Webhooks**. +The Azure REST API guidelines are an extension of the Microsoft-wide [OneAPI guidelines][1]. Readers of this document are assumed to be also reading the [OneAPI guidelines][1] and be familiar with them. Azure guidance is a superset of OneAPI guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. #### Asynchronous operations -The OneAPI guidelines for Long Running Operations guidelines are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the board decided to rename the HEADER used in the operation from `Azure-AsyncOperation` to `Operation-Location`. Services can and **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. +The OneAPI guidelines for Long Running Operations guidelines are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. ### Additional guidance for Azure Resource Manager resource providers -Teams building ARM RPs MUST follow the additional guidance in the ARM RPC and related documents. These documents can be found here. +Teams building ARM Resource Providers (RPs) MUST follow the additional guidance in the ARM RPC and related documents. These documents can be found here. * [Azure Resource Manager Wiki][2] (Internal only) * [Azure Resource Provider Contract][3] @@ -104,7 +85,7 @@ http(s)://-..azure.net ## Versioning -All Azure APIs **MUST** support explicit versioning. It's critical that clients can count on services to be stable over time, and it's critical that Azure services can add features and make changes. +All Azure APIs **MUST** use explicit versioning. It's critical that clients can count on services to be stable over time, and it's critical that Azure services can add features and make changes. The OneAPI guidelines offer a couple of different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes which options are required of Azure services as well as some guidance about deprecation policy. There is also a section about additional versioning practices necessary to support Azure Stack and Azure compatibility. @@ -122,7 +103,7 @@ POST http://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). -Even though we recommend clients ignore new fields, there are many libraries and clients that are strict. Therefore, Azure services **MUST** update the version number of their API even when adding optional fields. In fact, servers should be as strict as possible. Ignoring a field can result in the API accepting content that containered a typo or an element at the wrong level of nesting. If this missing field changes the semantics (for example, we have seen cases where security settings were misplaced and ignored, leaving the resources more exposed than intended) this can be a huge and hard to discover error. +Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Azure services **MUST** update the version number of their API even when adding optional fields. In fact, servers should be as strict as possible. Ignoring a field can result in the API accepting content that containered a typo or an element at the wrong level of nesting. If this missing field changes the semantics (for example, we have seen cases where security settings were misplaced and ignored, leaving the resources more exposed than intended) this can be a huge and hard to discover error. At a high level, changes to the contract of an API constitute a breaking change. Changes that impact backwards compatibility of an API is also considered a breaking change. Teams MAY define backwards compatibility as their business needs require. For example, Azure defines the addition of a new JSON field in a response to be not backwards compatible. Anything that would violate the Principle of Least Astonishment is considered a breaking change in Azure. Below are some concrete examples of what constitutes a breaking change. In the below breaking change scenarios, the API version must be changed. @@ -132,7 +113,7 @@ If a property called `foo` that was present in v1 of the API needs to be removed #### New property added to response -If a new property/field is added to the response an API, the GET-PUT pipeline will be broken. Consider the case where from portal a customer updates the value of a new property "A". Another customer does a GET of this resource using the SDK. The SDK will ignore the property since it does not understand it. From the SDK, the customer does a PUT using the model that was returned from the GET. This will overwrite the change made by the first customer from the portal. +If a new property/field is added to the response of an API, the GET-PUT pipeline will be broken. Consider the case where a customer updates the value of a new property "A" from the Azure portal. Another customer does a GET of this resource using the SDK. The SDK will ignore the property since it does not understand it. From the SDK, the customer does a PUT using the model that was returned from the GET. This will overwrite the change made by the first customer from the portal. #### New required property added to request @@ -201,11 +182,11 @@ Azure services **SHOULD** support API version discovery. If they support it: 1. Services **MUST** support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service 2. Services **MUST** include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. -3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it must include those versions (group and major.minor) in the `api-deprecated-versions` header. -4. In addition to the functionality described here, services may support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. -5. Services may allow unauthenticated HTTP `OPTIONS` requests. When doing so authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. -6. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends should report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. -7. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request should return the group versions that are supported across the expanded set. +3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it **MUST** include those versions (group and major.minor) in the `api-deprecated-versions` header. +4. In addition to the functionality described here, services **MAY** support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. +5. Services **MAY** allow unauthenticated HTTP `OPTIONS` requests. When doing so, authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. +6. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. +7. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request **SHOULD** return the group versions that are supported across the expanded set. Example request to discover versions (blob storage container list API): @@ -245,7 +226,7 @@ Refer to the Azure deprecation policy for more details. Pre-release and beta APIs are not covered by the normal API deprecation policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. -Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy and process. ** *Customers have suffered downtime because of deprecation of preview APIs* **. In some cases the code was written by consultants or employees without any awareness on behalf of the customer. +Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy and process. ** *Customers have suffered downtime because of deprecation of preview APIs* **. [1]: https://github.com/microsoft/api-guidelines From 8616ddb296e3ad5718f9c34edb1dad89c1b1841b Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Wed, 1 Apr 2020 09:22:04 -0700 Subject: [PATCH 032/729] Fixed issues with OneAPI and Swagger --- azure/Guidelines.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 1adbf1b8..d1ccbb74 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -7,15 +7,15 @@ ## Introduction -The Azure REST API guidelines are an extension of the Microsoft-wide [OneAPI guidelines][1]. Readers of this document are assumed to be also reading the [OneAPI guidelines][1] and be familiar with them. Azure guidance is a superset of OneAPI guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. +The Azure REST API guidelines are an extension of the [Microsoft API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. #### Asynchronous operations -The OneAPI guidelines for Long Running Operations guidelines are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. +The Microsoft API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. ### Additional guidance for Azure Resource Manager resource providers -Teams building ARM Resource Providers (RPs) MUST follow the additional guidance in the ARM RPC and related documents. These documents can be found here. +Teams building ARM Resource Providers (RPs) MUST follow the additional guidance in the ARM Resource Provider Contract (RPC) and related documents. These documents can be found here. * [Azure Resource Manager Wiki][2] (Internal only) * [Azure Resource Provider Contract][3] @@ -24,11 +24,11 @@ ARM RPs are a CEC requirement for Azure Services and ARM RP review is another ma ## Swagger to describe API -All Services **MUST** provide Swagger that describes their service. Swagger is a key element of the Azure SDK plan and essential to improving the usability and discoverability of services. Swagger is a CEC requirement for Azure Services. +All Services **MUST** provide as [OpenAPI Specification] that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the usability and discoverability of services. ## URL structure -In addition to the URL structure guidance in the OneAPI guidelines, Azure has specific guidance about service exposure for multi-tenant services +In addition to the URL structure guidance in the Microsoft API guidelines, Azure has specific guidance about service exposure for multi-tenant services ### URL structure @@ -87,11 +87,11 @@ http(s)://-..azure.net All Azure APIs **MUST** use explicit versioning. It's critical that clients can count on services to be stable over time, and it's critical that Azure services can add features and make changes. -The OneAPI guidelines offer a couple of different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes which options are required of Azure services as well as some guidance about deprecation policy. There is also a section about additional versioning practices necessary to support Azure Stack and Azure compatibility. +The Microsoft API guidelines offer a couple of different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes which options are required of Azure services as well as some guidance about deprecation policy. There is also a section about additional versioning practices necessary to support Azure Stack and Azure compatibility. ### Specifying the version in Azure -The OneAPI guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: +The Microsoft API guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: ``` GET http://blobstore.azure.com/foo.com/acct1/c1/blob2?api-version=1.0 @@ -236,5 +236,8 @@ Though services may set their own deprecation policy for pre-release APIs, they [2]: https://aka.ms/armwiki [3]: https://github.com/Azure/azure-resource-manager-rpc + +[OpenAPI Specification]: https://github.com/Azure/adx-documentation-pr/wiki/Getting-started-with-OpenAPI-specifications + [6]: http://support.microsoft.com/gp/azure-cloud-lifecycle-faq From de02fdd8572f74839a5cad0fe71eecd10f49a634 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Wed, 1 Apr 2020 13:46:01 -0700 Subject: [PATCH 033/729] Updates based on review by @cleemullins --- azure/Guidelines.md | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index d1ccbb74..2090aea5 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,17 +1,17 @@ -# Microsoft Azure REST Design Guidelines +# Microsoft Azure REST API Guidelines ## History | Date | Version | Notes | -| 2020-Mar-31 | v3.1 | Merge of Azure guidelines with breaking changes update | +| 2020-Mar-31 | v3.1 | 1st public release of the Azure REST API Guidelines| ## Introduction -The Azure REST API guidelines are an extension of the [Microsoft API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. +The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. #### Asynchronous operations -The Microsoft API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. +The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. ### Additional guidance for Azure Resource Manager resource providers @@ -22,20 +22,20 @@ Teams building ARM Resource Providers (RPs) MUST follow the additional guidance ARM RPs are a CEC requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. -## Swagger to describe API +## Use OpenAPI Specification to describe API -All Services **MUST** provide as [OpenAPI Specification] that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the usability and discoverability of services. +All Services **MUST** provide an [OpenAPI Specification] that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the usability and discoverability of services. ## URL structure -In addition to the URL structure guidance in the Microsoft API guidelines, Azure has specific guidance about service exposure for multi-tenant services +In addition to the URL structure guidance in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services ### URL structure All services **MUST** expose their service to developers via the following URL pattern: ``` -http(s)://.azure.net// +https://.azure.net// ``` Where: @@ -60,7 +60,7 @@ In addition to the required format above, services **MAY** also choose to expose The format of the root of the direct endpoint **MUST** be as follows: ``` -http(s)://-..azure.net +https://-..azure.net ``` 1. A request is made to the default end point (GET or HEAD). For example: @@ -73,30 +73,28 @@ http(s)://-..azure.net ``` 200 OK - Content-Location: http://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 + Content-Location: https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 ``` Or, with the GUID format: ``` 200 OK - Content-Location: http://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 + Content-Location: https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 ``` ## Versioning -All Azure APIs **MUST** use explicit versioning. It's critical that clients can count on services to be stable over time, and it's critical that Azure services can add features and make changes. - -The Microsoft API guidelines offer a couple of different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes which options are required of Azure services as well as some guidance about deprecation policy. There is also a section about additional versioning practices necessary to support Azure Stack and Azure compatibility. +All Azure APIs **MUST** use explicit versioning. The Microsoft REST API guidelines offer different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes updates those guidelines to ensure consistency between Azure services across Azure Stack, public Azure, and sovereign clouds. ### Specifying the version in Azure -The Microsoft API guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: +The Microsoft REST API guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: ``` -GET http://blobstore.azure.com/foo.com/acct1/c1/blob2?api-version=1.0 -PUT http://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 -POST http://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 +GET https://blobstore.azure.com/foo.com/acct1/c1/blob2?api-version=1.0 +PUT https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 +POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 ``` ### Breaking changes in Azure @@ -230,7 +228,7 @@ Though services may set their own deprecation policy for pre-release APIs, they [1]: https://github.com/microsoft/api-guidelines -[RFC2557]: http://www.ietf.org/rfc/rfc2557.txt +[RFC2557]: https://www.ietf.org/rfc/rfc2557.txt [2]: https://aka.ms/armwiki @@ -240,4 +238,4 @@ Though services may set their own deprecation policy for pre-release APIs, they [OpenAPI Specification]: https://github.com/Azure/adx-documentation-pr/wiki/Getting-started-with-OpenAPI-specifications -[6]: http://support.microsoft.com/gp/azure-cloud-lifecycle-faq +[6]: https://support.microsoft.com/gp/azure-cloud-lifecycle-faq From 0b1eb6b8d8877c638457198f5560c19103d6acb5 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Wed, 1 Apr 2020 13:55:31 -0700 Subject: [PATCH 034/729] Define CEC --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 2090aea5..fe2a3583 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -20,7 +20,7 @@ Teams building ARM Resource Providers (RPs) MUST follow the additional guidance * [Azure Resource Manager Wiki][2] (Internal only) * [Azure Resource Provider Contract][3] -ARM RPs are a CEC requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. +ARM RPs are a Common Engineering Criteria (CEC) requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. ## Use OpenAPI Specification to describe API From 92a6c1f51268bc8fe161613aeff0a5d1910ce366 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 6 Apr 2020 10:38:21 -0700 Subject: [PATCH 035/729] Apply suggestions from code review Co-Authored-By: Darrel --- azure/Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index fe2a3583..1d4657ea 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -22,9 +22,9 @@ Teams building ARM Resource Providers (RPs) MUST follow the additional guidance ARM RPs are a Common Engineering Criteria (CEC) requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. -## Use OpenAPI Specification to describe API +## Use an OpenAPI definition to describe API -All Services **MUST** provide an [OpenAPI Specification] that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the usability and discoverability of services. +All Services **MUST** provide an [OpenAPI Definition] that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the usability and discoverability of services. ## URL structure From e7e80e313b109377fb86e8efedf15e8e07e576bc Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 6 Apr 2020 10:40:29 -0700 Subject: [PATCH 036/729] Fixed numbering rendering --- azure/Guidelines.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 1d4657ea..24a7027a 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -179,12 +179,12 @@ API version discovery is needed when either a given hosted service may expose a Azure services **SHOULD** support API version discovery. If they support it: 1. Services **MUST** support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service -2. Services **MUST** include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. -3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it **MUST** include those versions (group and major.minor) in the `api-deprecated-versions` header. -4. In addition to the functionality described here, services **MAY** support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. -5. Services **MAY** allow unauthenticated HTTP `OPTIONS` requests. When doing so, authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. -6. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. -7. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request **SHOULD** return the group versions that are supported across the expanded set. +2. Services **MUST** include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. +3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it **MUST** include those versions (group and major.minor) in the `api-deprecated-versions` header. +4. In addition to the functionality described here, services **MAY** support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. +5. Services **MAY** allow unauthenticated HTTP `OPTIONS` requests. When doing so, authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. +6. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. +7. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request **SHOULD** return the group versions that are supported across the expanded set. Example request to discover versions (blob storage container list API): From 206a874f138ad70d1526ec383d297bdeb326a155 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 6 Apr 2020 13:47:37 -0700 Subject: [PATCH 037/729] Updated based on review comments --- azure/Guidelines.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 24a7027a..8317f3e2 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -20,11 +20,11 @@ Teams building ARM Resource Providers (RPs) MUST follow the additional guidance * [Azure Resource Manager Wiki][2] (Internal only) * [Azure Resource Provider Contract][3] -ARM RPs are a Common Engineering Criteria (CEC) requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. +ARM RPs are Azure Fundamentals requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. ## Use an OpenAPI definition to describe API -All Services **MUST** provide an [OpenAPI Definition] that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the usability and discoverability of services. +All Services **MUST** provide an [OpenAPI Definition] that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. ## URL structure @@ -35,12 +35,13 @@ In addition to the URL structure guidance in the Microsoft REST API guidelines, All services **MUST** expose their service to developers via the following URL pattern: ``` -https://.azure.net// +https://.// ``` Where: * **service** - the name of the service such as "blobstore", "servicebus", "directory", or "management" +* **cloud-instance** - the DNS domain name at the root of the cloud instance. For instance, public Azure uses `azure.net`. Sovereign clouds uses different domains. * **service-defined-root** - the root of the service-specific path, such as "blobcontainer", "myqueue", etc. * **unit-of-multi-tenancy** - refers to a globally unique moniker that identifies a unique container in the Azure service that has the following properties: @@ -103,7 +104,7 @@ A breaking change is any change in the API that may cause client or service code Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Azure services **MUST** update the version number of their API even when adding optional fields. In fact, servers should be as strict as possible. Ignoring a field can result in the API accepting content that containered a typo or an element at the wrong level of nesting. If this missing field changes the semantics (for example, we have seen cases where security settings were misplaced and ignored, leaving the resources more exposed than intended) this can be a huge and hard to discover error. -At a high level, changes to the contract of an API constitute a breaking change. Changes that impact backwards compatibility of an API is also considered a breaking change. Teams MAY define backwards compatibility as their business needs require. For example, Azure defines the addition of a new JSON field in a response to be not backwards compatible. Anything that would violate the Principle of Least Astonishment is considered a breaking change in Azure. Below are some concrete examples of what constitutes a breaking change. In the below breaking change scenarios, the API version must be changed. +At a high level, changes to the contract of an API constitute a breaking change. Changes that impact backwards compatibility of an API is also considered a breaking change. Anything that would violate the Principle of Least Astonishment is considered a breaking change in Azure. Below are some concrete examples of what constitutes a breaking change. In the below breaking change scenarios, the API version must be changed. #### Existing property is removed @@ -210,9 +211,9 @@ Disabling a runtime REST API that customers are dependent on of course has the p ### Policy -Azure does not have a single SLA for how long we will support all services. However, we have published expectations such as [the Azure Cloud Lifecycle FAQ][6]. The most relevant section of the document: +Azure does not have a single SLA for how long we will support all services. However, we have published expectations such as [the Azure Modern Lifecycle Policy][6]. The most relevant section of the document: -> Azure Cloud Services will support no fewer than the latest two SDK versions for deploying new Cloud Services. Microsoft will provide notification 12 months before retiring a SDK in order to smooth the transition to a supported version. +> For products governed by the Modern Lifecycle Policy, Microsoft will provide a minimum of 12 months' notification prior to ending support if no successor product or service is offered—excluding free services or preview releases. In practice, we have found this is a bare minimum of how long service endpoints must be supported. Services with any significant usage **SHOULD** expect to run until customers are no longer using them, which can be 10 years or more. @@ -238,4 +239,4 @@ Though services may set their own deprecation policy for pre-release APIs, they [OpenAPI Specification]: https://github.com/Azure/adx-documentation-pr/wiki/Getting-started-with-OpenAPI-specifications -[6]: https://support.microsoft.com/gp/azure-cloud-lifecycle-faq +[6]: https://support.microsoft.com/en-us/help/30881 From 4a19fe80cf24ea3775950228339503d591cb381e Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Wed, 8 Apr 2020 08:52:25 -0700 Subject: [PATCH 038/729] Adjustments from code review --- azure/Guidelines.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 8317f3e2..a24e78cf 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -9,10 +9,6 @@ The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. -#### Asynchronous operations - -The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. - ### Additional guidance for Azure Resource Manager resource providers Teams building ARM Resource Providers (RPs) MUST follow the additional guidance in the ARM Resource Provider Contract (RPC) and related documents. These documents can be found here. @@ -22,13 +18,13 @@ Teams building ARM Resource Providers (RPs) MUST follow the additional guidance ARM RPs are Azure Fundamentals requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. -## Use an OpenAPI definition to describe API +## API definition -All Services **MUST** provide an [OpenAPI Definition] that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. +All Services **MUST** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. ## URL structure -In addition to the URL structure guidance in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services +In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services ### URL structure @@ -205,6 +201,10 @@ Content-Length: 0 Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. +## Long running operations + +The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. + ## API deprecation policy Disabling a runtime REST API that customers are dependent on of course has the potential of breaking their applications or services, perhaps even mission critical services. But inevitably our APIs will become obsolete and the cost of supporting them and operating the servers on which they run will require us to deprecate and shut them down. We have a public policy that describes how we will inform customers that deprecation is coming and help them move their applications off these services and on to their replacements. From 6084b108a4c0d260f826043cf854a84d8b13d1d0 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Thu, 9 Apr 2020 13:19:46 -0700 Subject: [PATCH 039/729] Added v3.0 of the Azure REST API Guidelines (#191) * Added v3.0 of the Azure REST API Guidelines * Updates based on feedback from @johngossman * clarified the document title of the azure-cloud-lifecycle-faq * Updated links to ARM RPC * Added breaking change updates * Updates based on review from @garethj-msft * Fixed issues with OneAPI and Swagger * Updates based on review by @cleemullins * Define CEC * Apply suggestions from code review Co-Authored-By: Darrel * Fixed numbering rendering * Updated based on review comments * Adjustments from code review Co-authored-by: Darrel --- azure/Guidelines.md | 242 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 azure/Guidelines.md diff --git a/azure/Guidelines.md b/azure/Guidelines.md new file mode 100644 index 00000000..a24e78cf --- /dev/null +++ b/azure/Guidelines.md @@ -0,0 +1,242 @@ +# Microsoft Azure REST API Guidelines + +## History + +| Date | Version | Notes | +| 2020-Mar-31 | v3.1 | 1st public release of the Azure REST API Guidelines| + +## Introduction + +The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. + +### Additional guidance for Azure Resource Manager resource providers + +Teams building ARM Resource Providers (RPs) MUST follow the additional guidance in the ARM Resource Provider Contract (RPC) and related documents. These documents can be found here. + +* [Azure Resource Manager Wiki][2] (Internal only) +* [Azure Resource Provider Contract][3] + +ARM RPs are Azure Fundamentals requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. + +## API definition + +All Services **MUST** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. + +## URL structure + +In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services + +### URL structure + +All services **MUST** expose their service to developers via the following URL pattern: + +``` +https://.// +``` + +Where: + +* **service** - the name of the service such as "blobstore", "servicebus", "directory", or "management" +* **cloud-instance** - the DNS domain name at the root of the cloud instance. For instance, public Azure uses `azure.net`. Sovereign clouds uses different domains. +* **service-defined-root** - the root of the service-specific path, such as "blobcontainer", "myqueue", etc. +* **unit-of-multi-tenancy** - refers to a globally unique moniker that identifies a unique container in the Azure service that has the following properties: + + * This container is the boundary of isolation between different tenants of the service. + * Quotas as set and enforced at the level of this container - but there will be different limits for different operations; and operations will be service specific. + * Resources in the service are attached to this container and are tied to this container in terms of lifecycle. For example someone signs up, they get this container. If they unsubscribe (or don’t pay their bills) then cleanup of this container occurs and the resources associated with this container are cleaned up. Cleanup follows a state machine – the container and the resources attached to it are deactivated first (and can be easily restored if required), and if no response for some period then deleted. + * It is the container for billing – which means the owner of this container sees one bill for the resource usage of all azure services under this container’s identifier. + +For Azure PaaS services like SQL Azure, Azure Storage, Caching, etc., the unit of multi-tenancy is the Azure subscription id, which is a GUID. This ensures consistent access using the same URL pattern, and identifier, across all these services. + +When services produce URLs in response headers or bodies, they **MUST** use a consistent form – either always a GUID for tenant identifier or always a single verified domain - regardless of the URL used to reach the resource. + +### Direct endpoint URLs + +In addition to the required format above, services **MAY** also choose to expose direct endpoint for performance or routing reasons. The direct endpoint should be discoverable by clients, to ensure that developers are presented with a consistent pattern for accessing Azure services. + +The format of the root of the direct endpoint **MUST** be as follows: + +``` +https://-..azure.net +``` + +1. A request is made to the default end point (GET or HEAD). For example: + + ``` + GET https://blobstore.azure.net/contoso.com/account1/container1/blob2 + ``` + +2. That request is returned with the `Content-Location` header set to the direct endpoint. See [RFC2557]: + + ``` + 200 OK + Content-Location: https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 + ``` + + Or, with the GUID format: + + ``` + 200 OK + Content-Location: https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 + ``` + +## Versioning + +All Azure APIs **MUST** use explicit versioning. The Microsoft REST API guidelines offer different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes updates those guidelines to ensure consistency between Azure services across Azure Stack, public Azure, and sovereign clouds. + +### Specifying the version in Azure + +The Microsoft REST API guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: + +``` +GET https://blobstore.azure.com/foo.com/acct1/c1/blob2?api-version=1.0 +PUT https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 +POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 +``` + +### Breaking changes in Azure + +A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). + +Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Azure services **MUST** update the version number of their API even when adding optional fields. In fact, servers should be as strict as possible. Ignoring a field can result in the API accepting content that containered a typo or an element at the wrong level of nesting. If this missing field changes the semantics (for example, we have seen cases where security settings were misplaced and ignored, leaving the resources more exposed than intended) this can be a huge and hard to discover error. + +At a high level, changes to the contract of an API constitute a breaking change. Changes that impact backwards compatibility of an API is also considered a breaking change. Anything that would violate the Principle of Least Astonishment is considered a breaking change in Azure. Below are some concrete examples of what constitutes a breaking change. In the below breaking change scenarios, the API version must be changed. + +#### Existing property is removed + +If a property called `foo` that was present in v1 of the API needs to be removed, it must be done in a newer API version. + +#### New property added to response + +If a new property/field is added to the response of an API, the GET-PUT pipeline will be broken. Consider the case where a customer updates the value of a new property "A" from the Azure portal. Another customer does a GET of this resource using the SDK. The SDK will ignore the property since it does not understand it. From the SDK, the customer does a PUT using the model that was returned from the GET. This will overwrite the change made by the first customer from the portal. + +#### New required property added to request + +If a new property is made required in the request body, clients will have no way to set this and the request will fail. + +#### Property name has changed + +Note that this is implied by the requirement that adding/and removing properties are breaking changes, but in some ways worse, since it leads to the possibility of reusing a property name. Even with an API version change, this change is discouraged because it creates documentation and cognitive challenges. + +#### Property type has changed + +Property `foo was a boolean in v1 but is changed to a string. A client using the existing API version tries to set it as a boolean, but the service will fail since its now expecting a string. So, the API version must be updated. + +#### Property default value has changed + +If a property is optional and the service provides a default value, changing that default requires an updated API version. + +#### Allowed values for an enum have changed +Enum “foo” had allowed values as “val1” and “val2” in v1 of API. Now, the values accepted by the service are “val1”, “val2” and “val3”. The client will fail to de-serialize if “val3” comes back in the response. + +#### API has been removed or renamed + +V1 of API contract supported `PUT /resourceType1/{resourceType1_name}` but the service no longer supports this method. This scenario should follow the proper Azure API deprecation policy and must be done in an updated API version. + +#### Behavior of existing API has changed + +There is a functional change in what the API was doing. This is a complex issue because it sometimes is not an easy option to maintain the old behavior even on an old API version. It also is very confusing to end users even when version is update and documented. Behavior changes need to be well justified and discussed on a case-by-case basis. + +#### Error contracts have changed + +#### Property is made required (from optional) + +If property “foo” was optional in the request body of v1 and now it is required, this should result in an API version change. If not changed, clients relying on the older API version will fail if this property is not passed. + +#### URL format has changed + +Resource parameter names change from `/resourceType1/{resourceType1_name}` to `/resourceType1/{resourceType1_id}`. This will impact code generation. + +#### Resource naming rules should not change + +This could result in failures which would have earlier succeeded. Even if the rules become less strict, clients relying on earlier name constraints to perform local validation will fail. + +### Non-Breaking Changes + +The following changes are considered backwards compatible and hence non-breaking. + +#### Adding new APIs to an existing service + +When a new resource types is added, it does not require API version to be updated for existing types. + +#### Bug fixes to existing API + +Bug fixes to existing API which don’t fall into one of the above categories of breaking changes as described above are fine. + +### Group versioning in Azure and Azure Stack + +Azure Stack allows customers and hosters to deploy their own small versions of Azure and upgrade it at a different pace than Azure. In order to make it possible to write an application or SDK that targets both Azure and Azure Stack, additional versioning policy is necessary. Contact the Azure Stack team for further guidance. + +### Version discovery + +Simpler clients may be hardcoded to a single version of a service. Since Azure services offer each version for a well-known period of time, a client that’s regularly maintained can be always operational without further complexity as long as during regular maintenance the client is moved forward to new versions in advance of older ones being retired. + +API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. + +Azure services **SHOULD** support API version discovery. If they support it: + +1. Services **MUST** support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service +2. Services **MUST** include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. +3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it **MUST** include those versions (group and major.minor) in the `api-deprecated-versions` header. +4. In addition to the functionality described here, services **MAY** support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. +5. Services **MAY** allow unauthenticated HTTP `OPTIONS` requests. When doing so, authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. +6. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. +7. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request **SHOULD** return the group versions that are supported across the expanded set. + +Example request to discover versions (blob storage container list API): + +``` +OPTIONS /?comp=list HTTP/1.1 +host: accountname.blob.core.azure.net +``` + +Example response: + +``` +200 OK +api-supported-versions: 2011-08,2012-02,1.1,2.0 +api-deprecated-versions: 2009-04,1.0 +Content-Length: 0 +``` + +Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. + +## Long running operations + +The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. + +## API deprecation policy + +Disabling a runtime REST API that customers are dependent on of course has the potential of breaking their applications or services, perhaps even mission critical services. But inevitably our APIs will become obsolete and the cost of supporting them and operating the servers on which they run will require us to deprecate and shut them down. We have a public policy that describes how we will inform customers that deprecation is coming and help them move their applications off these services and on to their replacements. + +### Policy + +Azure does not have a single SLA for how long we will support all services. However, we have published expectations such as [the Azure Modern Lifecycle Policy][6]. The most relevant section of the document: + +> For products governed by the Modern Lifecycle Policy, Microsoft will provide a minimum of 12 months' notification prior to ending support if no successor product or service is offered—excluding free services or preview releases. + +In practice, we have found this is a bare minimum of how long service endpoints must be supported. Services with any significant usage **SHOULD** expect to run until customers are no longer using them, which can be 10 years or more. + +Service teams **MUST** contact the Azure API review board before communicating the deprecation externally to customers and partners (which starts the 12 month clock). + +Refer to the Azure deprecation policy for more details. + +#### Special case for pre-release and beta APIs + +Pre-release and beta APIs are not covered by the normal API deprecation policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. + +Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy and process. ** *Customers have suffered downtime because of deprecation of preview APIs* **. + + +[1]: https://github.com/microsoft/api-guidelines +[RFC2557]: https://www.ietf.org/rfc/rfc2557.txt + + +[2]: https://aka.ms/armwiki +[3]: https://github.com/Azure/azure-resource-manager-rpc + + +[OpenAPI Specification]: https://github.com/Azure/adx-documentation-pr/wiki/Getting-started-with-OpenAPI-specifications + + +[6]: https://support.microsoft.com/en-us/help/30881 From 7e63c970b5763f8ffdf4ac276f598ee4d13a667d Mon Sep 17 00:00:00 2001 From: Aditya Date: Fri, 10 Apr 2020 02:07:44 +0530 Subject: [PATCH 040/729] Use https for external links wherever possible (#151) Thanks much! --- CONTRIBUTING.md | 4 +-- Guidelines.md | 91 +++++++++++++++++++++++++------------------------ 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33a882ea..618d39e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,11 +92,11 @@ Be prepared to address feedback on your pull request and iterate if necessary. [new-issue]: https://github.com/Microsoft/api-guidelines/issues/new [issue-search]: https://github.com/Microsoft/api-guidelines/issues [white-house-api-guidelines]: https://github.com/WhiteHouse/api-standards/blob/master/README.md -[topic-branch]: http://www.git-scm.com/book/en/v2/Git-Branching-Branching-Workflows#Topic-Branches +[topic-branch]: https://www.git-scm.com/book/en/v2/Git-Branching-Branching-Workflows#Topic-Branches [gfm]: https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown [github-flow-guide]: https://guides.github.com/introduction/flow/ [atom-beautify]: https://atom.io/packages/atom-beautify -[atom]: http://atom.io +[atom]: https://atom.io/ [markdown-toc]: https://atom.io/packages/markdown-toc [vscode]: https://code.visualstudio.com/ [git]: https://git-scm.com/ diff --git a/Guidelines.md b/Guidelines.md index f9587c52..298fc08f 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -216,12 +216,12 @@ When a service adds a new API, that API SHOULD be consistent with the other APIs So if a service was written against version 1.0 of the guidelines, new APIs added incrementally to the service SHOULD also follow version 1.0. The service can then upgrade to align with the latest version of the guidelines at the service's next major release. ### 4.3. Requirements language -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). +The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119][rfc-2119]. ### 4.4. License This work is licensed under the Creative Commons Attribution 4.0 International License. -To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. +To view a copy of this license, visit https://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. ## 5. Taxonomy As part of onboarding to Microsoft REST API Guidelines, services MUST comply with the taxonomy defined below. @@ -259,7 +259,7 @@ To ensure the best possible experience for clients talking to a REST service, cl ### 6.1. Ignore rule For loosely coupled clients where the exact shape of the data is not known before the call, if the server returns something the client wasn't expecting, the client MUST safely ignore it. - + Some services MAY add fields to responses without changing versions numbers. Services that do so MUST make this clear in their documentation and clients MUST ignore unknown fields. @@ -312,9 +312,9 @@ From the RFC: Services that can generate URLs longer than 2,083 characters MUST make accommodations for the clients they wish to support. Here are some sources for determining what target clients support: - * [http://stackoverflow.com/a/417184](http://stackoverflow.com/a/417184) + * [https://stackoverflow.com/a/417184](https://stackoverflow.com/a/417184) * [https://blogs.msdn.microsoft.com/ieinternals/2014/08/13/url-length-limits/](https://blogs.msdn.microsoft.com/ieinternals/2014/08/13/url-length-limits/) - + Also note that some technology stacks have hard and adjustable URL limits, so keep this in mind as you design your services. ### 7.3. Canonical identifier @@ -1096,7 +1096,7 @@ If the delta link is no longer valid, the service MUST respond with _410 Gone_. ## 11. JSON standardizations ### 11.1. JSON formatting standardization for primitive types -Primitive values MUST be serialized to JSON following the rules of [RFC8259][rfc-8259]. +Primitive values MUST be serialized to JSON following the rules of [RFC8259][rfc-8259]. **Important note for 64bit integers:** JavaScript will silently truncate integers larger than `Number.MAX_SAFE_INTEGER` (2^53-1) or numbers smaller than `Number.MIN_SAFE_INTEGER` (-2^53+1). If the service is expected to return integer values outside the range of safe values, strongly consider returning the value as a string in order to maximize interoperability and avoid data loss. @@ -1123,7 +1123,7 @@ The Web has coalesced around the [ECMAScript subset of ISO 8601 date formats (IS For those cases, this document defines a JSON serialization format that can be used to unambiguously represent dates in different formats. Other serialization formats (such as XML) could be derived from this format. -#### 11.3.1. The `DateLiteral` format +#### 11.3.1. The `DateLiteral` format Dates represented in JSON are serialized using the following grammar. Informally, a `DateValue` is either an ISO 8601-formatted string or a JSON object containing two properties named `kind` and `value` that together define a point in time. The following is not a context-free grammar; in particular, the interpretation of `DateValue` depends on the value of `DateKind`, but this minimizes the number of productions required to describe the format. @@ -1134,7 +1134,7 @@ DateLiteral: StructuredDateLiteral Iso8601Literal: - A string literal as defined in http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15. Note that the full grammar for ISO 8601 (such as "basic format" without separators) is not supported. + A string literal as defined in https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15. Note that the full grammar for ISO 8601 (such as "basic format" without separators) is not supported. All dates default to UTC unless specified otherwise. StructuredDateLiteral: @@ -1543,7 +1543,7 @@ Services MAY choose to delete tombstones after a service defined period of time. #### 13.2.7. The typical flow, polling - Client invokes a stepwise operation by invoking an action using POST - The server MUST indicate the request has been started by responding with a 202 Accepted status code. The response SHOULD include the location header containing a URL that the client should poll for the results after waiting the number of seconds specified in the Retry-After header. -- Client polls the location until receiving a 200 response with a terminal operation state. +- Client polls the location until receiving a 200 response with a terminal operation state. ##### Example of the typical flow, polling Client invokes the restart action: @@ -1674,7 +1674,7 @@ Operations SHOULD transition to "tombstone" for an additional period of time pri ## 14. Throttling, Quotas, and Limits ### 14.1. Principles -Services should be as responsive as possible, so as not to block callers. +Services should be as responsive as possible, so as not to block callers. As a rule of thumb any API call that is expected to take longer than 0.5 seconds in the 99th percentile, should consider using the Long-running Operations pattern for those calls. Obviously, services cannot guarantee these response times in the face of potentially unlimited load from callers. Services should therefore design and document call request limits for clients, and respond with appropriate, actionable errors and error messages if these limits are exceeded. Services should respond quickly with an error when they are generally overloaded, rather than simply respond slowly. @@ -1696,7 +1696,7 @@ The Retry-After header is the standard way for responding to clients who are bei It is also common, but optional, in the case of limits and quotas (but not overall system load) to respond with header describing the limit that was exceeded. However, services across Microsoft and the industry use a wide range of different headers for this purpose. We recommend using three headers to describe the limit, the number of calls remaining under the limit, and the time when the limit will reset. -However, other headers may be appropriate for specific types of limits. In all cases these must be documented. +However, other headers may be appropriate for specific types of limits. In all cases these must be documented. ### 14.4. Service Guidance Services should choose time windows as appropriate for the SLAs or business objectives. @@ -1764,7 +1764,7 @@ See: https://docs.microsoft.com/en-us/azure/architecture/best-practices/transien Ideally, 429 and 503 returns are so low cost that even clients that retry immediately can be handled. In these cases, if possible the service team should make an effort to contact or fix the client. If it is a known partner, a bug or incident should be filed. -In extreme cases it may be necessary to use DoS style protections such as blocking the caller. +In extreme cases it may be necessary to use DoS style protections such as blocking the caller. ## 15. Push notifications via webhooks ### 15.1. Scope @@ -1823,8 +1823,8 @@ For a per-user subscription, app registration is either manual or automated. The call flow for a per-user subscription MUST follow the diagram below. It shows the end user making use of one of the service's APIs, and again, the same two things MUST be stored: -1. The service MUST store the end user's act of consent to receiving notifications from this specific application (typically a background usage OAUTH scope). -2. The subscribing application MUST store the end user's tokens in order to call back for details once notified of changes. +1. The service MUST store the end user's act of consent to receiving notifications from this specific application (typically a background usage OAUTH scope). +2. The subscribing application MUST store the end user's tokens in order to call back for details once notified of changes. In this case, the subscription is set up programmatically using the end-user's token from the subscribing application. The app MUST store the ID of the registered subscription alongside the user tokens. @@ -2208,13 +2208,13 @@ Service owners adding such terms SHOULD propose additions to this document. | | |------------- | attendees | - body | + body | createdDateTime | - childCount | - children | - contentUrl | - country | - createdBy | + childCount | + children | + contentUrl | + country | + createdBy | displayName | errorUrl | eTag | @@ -2222,16 +2222,16 @@ Service owners adding such terms SHOULD propose additions to this document. expirationDateTime | givenName | jobTitle | - kind | + kind | id | lastModifiedDateTime | location | memberOf | message | - name | + name | owner | - people | - person | + people | + person | postalCode | photo | preferredLanguage | @@ -2368,35 +2368,36 @@ note right of App Server: Update status and cache new "since" token === End Text === ``` [fielding]: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm -[IANA-headers]: http://www.iana.org/assignments/message-headers/message-headers.xhtml +[IANA-headers]: https://www.iana.org/assignments/message-headers/message-headers.xhtml +[rfc-2119]: https://tools.ietf.org/html/rfc2119 [rfc7231-7-1-1-1]: https://tools.ietf.org/html/rfc7231#section-7.1.1.1 [rfc-7230-3-1-1]: https://tools.ietf.org/html/rfc7230#section-3.1.1 [rfc-7231]: https://tools.ietf.org/html/rfc7231 -[rest-in-practice]: http://www.amazon.com/REST-Practice-Hypermedia-Systems-Architecture/dp/0596805829/ -[rest-on-wikipedia]: http://en.wikipedia.org/wiki/Representational_state_transfer -[rfc-5789]: http://tools.ietf.org/html/rfc5789 -[rfc-5988]: http://tools.ietf.org/html/rfc5988 +[rest-in-practice]: https://www.amazon.com/REST-Practice-Hypermedia-Systems-Architecture/dp/0596805829/ +[rest-on-wikipedia]: https://en.wikipedia.org/wiki/Representational_state_transfer +[rfc-5789]: https://tools.ietf.org/html/rfc5789 +[rfc-5988]: https://tools.ietf.org/html/rfc5988 [rfc-3339]: https://tools.ietf.org/html/rfc3339 [rfc-5322-3-3]: https://tools.ietf.org/html/rfc5322#section-3.3 -[cors-preflight]: http://www.w3.org/TR/cors/#resource-preflight-requests -[rfc-3864]: http://www.ietf.org/rfc/rfc3864.txt -[odata-json-annotations]: http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Instance_Annotations -[cors]: http://www.w3.org/TR/access-control/ -[cors-user-credentials]: http://www.w3.org/TR/access-control/#user-credentials -[cors-simple-headers]: http://www.w3.org/TR/access-control/#simple-header +[cors-preflight]: https://www.w3.org/TR/cors/#resource-preflight-requests +[rfc-3864]: https://tools.ietf.org/html/rfc3864 +[odata-json-annotations]: https://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Instance_Annotations +[cors]: https://www.w3.org/TR/access-control/ +[cors-user-credentials]: https://www.w3.org/TR/access-control/#user-credentials +[cors-simple-headers]: https://www.w3.org/TR/access-control/#simple-header [rfc-4627]: https://tools.ietf.org/html/rfc4627 -[iso-8601]: http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 +[iso-8601]: https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 [clr-time]: https://msdn.microsoft.com/en-us/library/System.DateTime(v=vs.110).aspx -[ecmascript-time]: http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.1 +[ecmascript-time]: https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.1 [ole-date]: https://docs.microsoft.com/en-us/windows/desktop/api/oleauto/nf-oleauto-varianttimetosystemtime [ticks-time]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx [unix-time]: https://msdn.microsoft.com/en-us/library/1f4c8f33.aspx [windows-time]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx -[excel-time]: http://support.microsoft.com/kb/214326?wa=wsignin1.0 -[wikipedia-iso8601-durations]: http://en.wikipedia.org/wiki/ISO_8601#Durations -[wikipedia-iso8601-intervals]: http://en.wikipedia.org/wiki/ISO_8601#Time_intervals -[wikipedia-iso8601-repeatingintervals]: http://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals -[principle-of-least-astonishment]: http://en.wikipedia.org/wiki/Principle_of_least_astonishment -[odata-breaking-changes]: http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209 -[websequencediagram-firehose-subscription-setup]: http://www.websequencediagrams.com/cgi-bin/cdraw?lz=bm90ZSBvdmVyIERldmVsb3BlciwgQXV0b21hdGlvbiwgQXBwIFNlcnZlcjogCiAgICAgQW4AEAUAJwkgbGlrZSBNb3ZpZU1ha2VyACAGV2FudHMgdG8gaW50ZWdyYXRlIHdpdGggcHJpbWFyeSBzZXJ2aWNlADcGRHJvcGJveAplbmQgbm90ZQoAgQwLQiBQb3J0YWwsIERCAIEJBVJlZ2lzdHIAgRkHREIgTm90aWZpYwCBLAVzACEGdXRoACsFUwBgBjogVGhlAF0eAIF_CkNsaWVudAAtBmVuZCB1c2VycycgYnJvd3NlciBvciBpbnN0YWxsZWQgYXBwCgCBIQwAgiQgAIFABQCBIS8AgQoGIDogTWFudWFsAIFzEQoKCgCDAgo8LS0-AIIqCiA6IExvZ2luIGludG8Agj8JAII1ECBVWCAKACoKLT4gKwCCWBM6AIQGBU5hbWUgZXRjLgCDFQ4AGxJDb25maXJtAIEBCEFjY2VzcyBUb2tlbgoKAIM3EyAtPiAtAINkCQBnBklEAIEMCwCBVQUAhQIMAIR3CmNvcGllcwArCACCIHAAhHMMAIMKDwCDABg6IHdlYmhvb2sgcgCCeg4AgnUSAIVQDToAhXYHZXIAgwgGAIcTBgBECVVSTCwgU2NvcGUAhzIGSUQKTgCGPQwAhhwNIACDBh4AHhEAgxEPbgCBagwAgxwNAIMaDiAAgx0MbWF5IGNvcHkALREAhVtqAIZHB0F1dGhvcml6AIY7BwCGXQctPiArAIEuDVJlcXVlc3QgYQCFOQZ0byBEQiBwcm90ZWN0ZWQgaW5mb3IAiiQGCgCDBQstPiAtAIctCVJlZGlyZWN0ADYHAGwNIGVuZHBvaW50AIoWBmEADw1yAHYGAIEQDACJVAcASwtlZAAYHgCICAgAMAcAcA4AhGoGAE0FAIEdFmJhY2sgdG8AhF8NaXRoIGNvZGUAghoaaQCBagcAgToHAD0JAII-B3MAPgsAglEHAEsFAIIzDgCBXw0Agn8GdG9rZW5zACcSAI0_BXJpZ2h0IG9mAItpDUNhY2hlIHRoYXQgdGhpcyBVc2VyIElEIHByb3ZpZGVkAINNCwCIZgoAggcJAIN7D3Nwb25zAI0_BwCECgYsIHJlZnJlc2gsIGFuZCBJRACBHAcAgQMPAIYADQCBDAcAgUUGYnkAjFkFIElEAIQkG3R1cm4AhF4MIHRvIGMAjR8FAIwRagCJVw1GbG93AIYqCQCMaQgAgmoKaGFuZ2UAj3YFAIFXBWRhdGEgLSB0eXBpY2FsIHZpYQCQDgVyYWN0aW5nAJAPBgCJQQt2aWEAjnsHCgCPNgogAIhDEACKZw0AkFMFAIkBDwCDDAUAgkYWKwBNCwCHWApjAIEyBQCHRg0AhWUHYWNoAIQeDACEfwVhbmQgInNpbmNlIgCFEQYAkSQOAIR3CgCNfwcAhHQFAIpQEACBUgsAhFAcAII8BWFuZCBuZXcAYRQAhFUTOiBVcGRhdGUgc3RhdHUAgSkGAIFDBQAxEwoKCg&s=mscgen -[websequencediagram-user-subscription-setup]: http://www.websequencediagrams.com/cgi-bin/cdraw?lz=bm90ZSBvdmVyIERldmVsb3BlciwgQXV0b21hdGlvbiwgQXBwIFNlcnZlcjogCiAgICAgQW4AEAUAJwkgbGlrZSBNb3ZpZU1ha2VyACAGV2FudHMgdG8gaW50ZWdyYXRlIHdpdGggcHJpbWFyeSBzZXJ2aWNlADcGRHJvcGJveAplbmQgbm90ZQoAgQwLQiBQb3J0YWwsIERCAIEJBVJlZ2lzdHIAgRkHREIgTm90aWZpYwCBLAVzACEGdXRoACsFUwBgBjogVGhlAF0eAIF_CkNsaWVudAAtBmVuZCB1c2VycycgYnJvd3NlciBvciBpbnN0YWxsZWQgYXBwCgCBIQwAgiQgAIFABQCBIS8AgQoGIDoAgWwRCgphbHQAgyUIAIEHBiByABQMICAAgxsLPC0tPgCDTws6IENvbmZpZ3VyZQogIACDaAsgLT4gKwCCWBMAegZOYW1lIGV0Yy4AhAgFAIMaDQAfEgBdBXJtAIQ_BUFjY2VzcyBUb2tlAIETBgCDOxIgLT4gLQCBFgxBcHAgSUQAhHwIY3JldACBGxAtPgCFFgsgOiBFbWJlZAAkFGVsc2UgTWFudWFsAIIEJACEbQkgOiBMb2dpbiBpbnRvAIUBCQCBKRFVWACGGAUALQoAgh8mAIIZKwCBCAcAgjoNAIIsHACGLwkAgj8IAIESDgCECAYAh1ELAIdFCmNvcGllcwAuCGVuZACEeGoAhWQHQXV0aG9yaXoAhV8HAIV6By0-ICsAg2ANUmVxdWVzdCBhAIRVBnRvIERCIHByb3RlY3RlZCBpbmZvcgCJQQYKAIQaCy0-IC0AhkoJUmVkaXJlY3QANgcAbA0gZW5kcG9pbnQAiTMGYQAPDXIAdgYAgRAMAIhxBwBLC2VkABgeAIRjCAAwB0EAcQxVWAoASQgAgRwWYmFjayB0bwCFdAwAilwFY29kZQCCGRppAIFpBwCBOQcAPQkAgj0HcwA-CwCCUAcASwUAgjIOAIFeDQCCfgZ0b2tlbnMAJxIAjFsFcmlnaHQgb2YAiwUNQ2FjaGUgdGhhdCB0aGlzIFVzZXIgSUQgcHJvdmlkZWQAg0wLAIU6BwCCBAwAg3oPc3BvbnMAjFsHAIQJBiwgcmVmcmVzaCwgYW5kIElEAIEcBwCBAw8AiDENAIEMBwCBRQZieQCLdQUgSUQAhCMbdHVybgCEXQwgdG8gYwCMOwUKCgCLL2oAjXUMAIwTDwCPNQotPisAjhwQOgCORQdlcgCMVwYAg3YIZWJob29rIFVSTCwgU2NvcGUAkAEGSUQAjwoOAI5rDSAAi2UKAINFBQCLYw0AHBEAgzUOOiBuAIE2DABgCACDCB1oZQCBaQ5JRACDYwUAahIAghB4RmxvdwCJMwkAjE0IAIV0CmhhbmdlAJIcBQCEYQVkYXRhIC0gdHlwaWNhbCB2aWEAkjQFcmFjdGluZwCSNQYAjV8LdmlhAJEhBwoAkVwKIACNfhAAhAsNAJJ5BQCCWQ8AhhYFAIVQFisATQsAimEKYwCBMgUAik8NAIhvB2FjaACHKAwAiAkFYW5kICJzaW5jZSIAiBsGAJNKDgCIAQoAhB0cAIFSCwCHWhwAgjwFYW5kIG5ldwBhFACHXxM6IFVwZGF0ZSBzdGF0dQCBKQYAgUMFADETCgoK&s=mscgen +[excel-time]: https://support.microsoft.com/kb/214326?wa=wsignin1.0 +[wikipedia-iso8601-durations]: https://en.wikipedia.org/wiki/ISO_8601#Durations +[wikipedia-iso8601-intervals]: https://en.wikipedia.org/wiki/ISO_8601#Time_intervals +[wikipedia-iso8601-repeatingintervals]: https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals +[principle-of-least-astonishment]: https://en.wikipedia.org/wiki/Principle_of_least_astonishment +[odata-breaking-changes]: https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209 +[websequencediagram-firehose-subscription-setup]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=bm90ZSBvdmVyIERldmVsb3BlciwgQXV0b21hdGlvbiwgQXBwIFNlcnZlcjogCiAgICAgQW4AEAUAJwkgbGlrZSBNb3ZpZU1ha2VyACAGV2FudHMgdG8gaW50ZWdyYXRlIHdpdGggcHJpbWFyeSBzZXJ2aWNlADcGRHJvcGJveAplbmQgbm90ZQoAgQwLQiBQb3J0YWwsIERCAIEJBVJlZ2lzdHIAgRkHREIgTm90aWZpYwCBLAVzACEGdXRoACsFUwBgBjogVGhlAF0eAIF_CkNsaWVudAAtBmVuZCB1c2VycycgYnJvd3NlciBvciBpbnN0YWxsZWQgYXBwCgCBIQwAgiQgAIFABQCBIS8AgQoGIDogTWFudWFsAIFzEQoKCgCDAgo8LS0-AIIqCiA6IExvZ2luIGludG8Agj8JAII1ECBVWCAKACoKLT4gKwCCWBM6AIQGBU5hbWUgZXRjLgCDFQ4AGxJDb25maXJtAIEBCEFjY2VzcyBUb2tlbgoKAIM3EyAtPiAtAINkCQBnBklEAIEMCwCBVQUAhQIMAIR3CmNvcGllcwArCACCIHAAhHMMAIMKDwCDABg6IHdlYmhvb2sgcgCCeg4AgnUSAIVQDToAhXYHZXIAgwgGAIcTBgBECVVSTCwgU2NvcGUAhzIGSUQKTgCGPQwAhhwNIACDBh4AHhEAgxEPbgCBagwAgxwNAIMaDiAAgx0MbWF5IGNvcHkALREAhVtqAIZHB0F1dGhvcml6AIY7BwCGXQctPiArAIEuDVJlcXVlc3QgYQCFOQZ0byBEQiBwcm90ZWN0ZWQgaW5mb3IAiiQGCgCDBQstPiAtAIctCVJlZGlyZWN0ADYHAGwNIGVuZHBvaW50AIoWBmEADw1yAHYGAIEQDACJVAcASwtlZAAYHgCICAgAMAcAcA4AhGoGAE0FAIEdFmJhY2sgdG8AhF8NaXRoIGNvZGUAghoaaQCBagcAgToHAD0JAII-B3MAPgsAglEHAEsFAIIzDgCBXw0Agn8GdG9rZW5zACcSAI0_BXJpZ2h0IG9mAItpDUNhY2hlIHRoYXQgdGhpcyBVc2VyIElEIHByb3ZpZGVkAINNCwCIZgoAggcJAIN7D3Nwb25zAI0_BwCECgYsIHJlZnJlc2gsIGFuZCBJRACBHAcAgQMPAIYADQCBDAcAgUUGYnkAjFkFIElEAIQkG3R1cm4AhF4MIHRvIGMAjR8FAIwRagCJVw1GbG93AIYqCQCMaQgAgmoKaGFuZ2UAj3YFAIFXBWRhdGEgLSB0eXBpY2FsIHZpYQCQDgVyYWN0aW5nAJAPBgCJQQt2aWEAjnsHCgCPNgogAIhDEACKZw0AkFMFAIkBDwCDDAUAgkYWKwBNCwCHWApjAIEyBQCHRg0AhWUHYWNoAIQeDACEfwVhbmQgInNpbmNlIgCFEQYAkSQOAIR3CgCNfwcAhHQFAIpQEACBUgsAhFAcAII8BWFuZCBuZXcAYRQAhFUTOiBVcGRhdGUgc3RhdHUAgSkGAIFDBQAxEwoKCg&s=mscgen +[websequencediagram-user-subscription-setup]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=bm90ZSBvdmVyIERldmVsb3BlciwgQXV0b21hdGlvbiwgQXBwIFNlcnZlcjogCiAgICAgQW4AEAUAJwkgbGlrZSBNb3ZpZU1ha2VyACAGV2FudHMgdG8gaW50ZWdyYXRlIHdpdGggcHJpbWFyeSBzZXJ2aWNlADcGRHJvcGJveAplbmQgbm90ZQoAgQwLQiBQb3J0YWwsIERCAIEJBVJlZ2lzdHIAgRkHREIgTm90aWZpYwCBLAVzACEGdXRoACsFUwBgBjogVGhlAF0eAIF_CkNsaWVudAAtBmVuZCB1c2VycycgYnJvd3NlciBvciBpbnN0YWxsZWQgYXBwCgCBIQwAgiQgAIFABQCBIS8AgQoGIDoAgWwRCgphbHQAgyUIAIEHBiByABQMICAAgxsLPC0tPgCDTws6IENvbmZpZ3VyZQogIACDaAsgLT4gKwCCWBMAegZOYW1lIGV0Yy4AhAgFAIMaDQAfEgBdBXJtAIQ_BUFjY2VzcyBUb2tlAIETBgCDOxIgLT4gLQCBFgxBcHAgSUQAhHwIY3JldACBGxAtPgCFFgsgOiBFbWJlZAAkFGVsc2UgTWFudWFsAIIEJACEbQkgOiBMb2dpbiBpbnRvAIUBCQCBKRFVWACGGAUALQoAgh8mAIIZKwCBCAcAgjoNAIIsHACGLwkAgj8IAIESDgCECAYAh1ELAIdFCmNvcGllcwAuCGVuZACEeGoAhWQHQXV0aG9yaXoAhV8HAIV6By0-ICsAg2ANUmVxdWVzdCBhAIRVBnRvIERCIHByb3RlY3RlZCBpbmZvcgCJQQYKAIQaCy0-IC0AhkoJUmVkaXJlY3QANgcAbA0gZW5kcG9pbnQAiTMGYQAPDXIAdgYAgRAMAIhxBwBLC2VkABgeAIRjCAAwB0EAcQxVWAoASQgAgRwWYmFjayB0bwCFdAwAilwFY29kZQCCGRppAIFpBwCBOQcAPQkAgj0HcwA-CwCCUAcASwUAgjIOAIFeDQCCfgZ0b2tlbnMAJxIAjFsFcmlnaHQgb2YAiwUNQ2FjaGUgdGhhdCB0aGlzIFVzZXIgSUQgcHJvdmlkZWQAg0wLAIU6BwCCBAwAg3oPc3BvbnMAjFsHAIQJBiwgcmVmcmVzaCwgYW5kIElEAIEcBwCBAw8AiDENAIEMBwCBRQZieQCLdQUgSUQAhCMbdHVybgCEXQwgdG8gYwCMOwUKCgCLL2oAjXUMAIwTDwCPNQotPisAjhwQOgCORQdlcgCMVwYAg3YIZWJob29rIFVSTCwgU2NvcGUAkAEGSUQAjwoOAI5rDSAAi2UKAINFBQCLYw0AHBEAgzUOOiBuAIE2DABgCACDCB1oZQCBaQ5JRACDYwUAahIAghB4RmxvdwCJMwkAjE0IAIV0CmhhbmdlAJIcBQCEYQVkYXRhIC0gdHlwaWNhbCB2aWEAkjQFcmFjdGluZwCSNQYAjV8LdmlhAJEhBwoAkVwKIACNfhAAhAsNAJJ5BQCCWQ8AhhYFAIVQFisATQsAimEKYwCBMgUAik8NAIhvB2FjaACHKAwAiAkFYW5kICJzaW5jZSIAiBsGAJNKDgCIAQoAhB0cAIFSCwCHWhwAgjwFYW5kIG5ldwBhFACHXxM6IFVwZGF0ZSBzdGF0dQCBKQYAgUMFADETCgoK&s=mscgen From 20f29e0f8b2e0272c535e6582302b9da5c5d572a Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Fri, 10 Apr 2020 11:40:54 -0700 Subject: [PATCH 041/729] Updated breaking change rules --- azure/Guidelines.md | 85 ++++++++++++----------------------- azure/breaking-changes.md.old | 66 +++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 56 deletions(-) create mode 100644 azure/breaking-changes.md.old diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a24e78cf..6b4916d9 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -94,74 +94,47 @@ PUT https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 ``` -### Breaking changes in Azure +### Breaking changes -A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). +A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. -Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Azure services **MUST** update the version number of their API even when adding optional fields. In fact, servers should be as strict as possible. Ignoring a field can result in the API accepting content that containered a typo or an element at the wrong level of nesting. If this missing field changes the semantics (for example, we have seen cases where security settings were misplaced and ignored, leaving the resources more exposed than intended) this can be a huge and hard to discover error. +Azure services **MUST** update the version number of their API whenever there is a change to the API, no matter how small. Customers will "lock the API version" so that their code does not fail when the service introduces new features. They rely on the fact that an API version is a contract with the services that will never change. -At a high level, changes to the contract of an API constitute a breaking change. Changes that impact backwards compatibility of an API is also considered a breaking change. Anything that would violate the Principle of Least Astonishment is considered a breaking change in Azure. Below are some concrete examples of what constitutes a breaking change. In the below breaking change scenarios, the API version must be changed. +There are three reasons why a service may issue a breaking change: -#### Existing property is removed +1. To remove a security vulnerability. +2. To comply with regulatory requirements. +3. To deprecate a feature of the service. -If a property called `foo` that was present in v1 of the API needs to be removed, it must be done in a newer API version. +In each case, prior approval of the Azure REST API review board is required. In the case of deprecation, follow the API deprecation policy (below). If the service is using SemVer for versioning, breaking changes constitute a major version change. -#### New property added to response +#### Examples of breaking changes -If a new property/field is added to the response of an API, the GET-PUT pipeline will be broken. Consider the case where a customer updates the value of a new property "A" from the Azure portal. Another customer does a GET of this resource using the SDK. The SDK will ignore the property since it does not understand it. From the SDK, the customer does a PUT using the model that was returned from the GET. This will overwrite the change made by the first customer from the portal. +At a high level, any change to the contract of an API constitutes a breaking change. The following is a non-exhaustive list of breaking changes: -#### New required property added to request +* An existing property is removed. +* A new property is added to an existing response. +* A new required property is added to an existing request. +* A property name is changed. +* A property type is changed. +* The default value of a property is changed. +* The allowed values for an enum is changed. +* An API is removed. +* An API is removed. +* The behavior of an existing API is changed. +* The error contract has changed. +* A property is made required (from optional) +* The URL format is changed. +* Resource naming rules have changed. -If a new property is made required in the request body, clients will have no way to set this and the request will fail. +#### Examples of non-breaking changes -#### Property name has changed +Not all changes are breaking. The following changes are considered backwards compatible and hence non-breaking. -Note that this is implied by the requirement that adding/and removing properties are breaking changes, but in some ways worse, since it leads to the possibility of reusing a property name. Even with an API version change, this change is discouraged because it creates documentation and cognitive challenges. +* Adding a new API to an existing service. +* Changing a property from required to optional. -#### Property type has changed - -Property `foo was a boolean in v1 but is changed to a string. A client using the existing API version tries to set it as a boolean, but the service will fail since its now expecting a string. So, the API version must be updated. - -#### Property default value has changed - -If a property is optional and the service provides a default value, changing that default requires an updated API version. - -#### Allowed values for an enum have changed -Enum “foo” had allowed values as “val1” and “val2” in v1 of API. Now, the values accepted by the service are “val1”, “val2” and “val3”. The client will fail to de-serialize if “val3” comes back in the response. - -#### API has been removed or renamed - -V1 of API contract supported `PUT /resourceType1/{resourceType1_name}` but the service no longer supports this method. This scenario should follow the proper Azure API deprecation policy and must be done in an updated API version. - -#### Behavior of existing API has changed - -There is a functional change in what the API was doing. This is a complex issue because it sometimes is not an easy option to maintain the old behavior even on an old API version. It also is very confusing to end users even when version is update and documented. Behavior changes need to be well justified and discussed on a case-by-case basis. - -#### Error contracts have changed - -#### Property is made required (from optional) - -If property “foo” was optional in the request body of v1 and now it is required, this should result in an API version change. If not changed, clients relying on the older API version will fail if this property is not passed. - -#### URL format has changed - -Resource parameter names change from `/resourceType1/{resourceType1_name}` to `/resourceType1/{resourceType1_id}`. This will impact code generation. - -#### Resource naming rules should not change - -This could result in failures which would have earlier succeeded. Even if the rules become less strict, clients relying on earlier name constraints to perform local validation will fail. - -### Non-Breaking Changes - -The following changes are considered backwards compatible and hence non-breaking. - -#### Adding new APIs to an existing service - -When a new resource types is added, it does not require API version to be updated for existing types. - -#### Bug fixes to existing API - -Bug fixes to existing API which don’t fall into one of the above categories of breaking changes as described above are fine. +In these cases, a new version number is still required. If using SemVer, it is appropriate to use a minor version change. ### Group versioning in Azure and Azure Stack diff --git a/azure/breaking-changes.md.old b/azure/breaking-changes.md.old new file mode 100644 index 00000000..5f5db488 --- /dev/null +++ b/azure/breaking-changes.md.old @@ -0,0 +1,66 @@ +A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). + +Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Azure services **MUST** update the version number of their API even when adding optional fields. In fact, servers should be as strict as possible. Ignoring a field can result in the API accepting content that containered a typo or an element at the wrong level of nesting. If this missing field changes the semantics (for example, we have seen cases where security settings were misplaced and ignored, leaving the resources more exposed than intended) this can be a huge and hard to discover error. + +At a high level, changes to the contract of an API constitute a breaking change. Changes that impact backwards compatibility of an API is also considered a breaking change. Anything that would violate the Principle of Least Astonishment is considered a breaking change in Azure. Below are some concrete examples of what constitutes a breaking change. In the below breaking change scenarios, the API version must be changed. + +#### Existing property is removed + +If a property called `foo` that was present in v1 of the API needs to be removed, it must be done in a newer API version. + +#### New property added to response + +If a new property/field is added to the response of an API, the GET-PUT pipeline will be broken. Consider the case where a customer updates the value of a new property "A" from the Azure portal. Another customer does a GET of this resource using the SDK. The SDK will ignore the property since it does not understand it. From the SDK, the customer does a PUT using the model that was returned from the GET. This will overwrite the change made by the first customer from the portal. + +#### New required property added to request + +If a new property is made required in the request body, clients will have no way to set this and the request will fail. + +#### Property name has changed + +Note that this is implied by the requirement that adding/and removing properties are breaking changes, but in some ways worse, since it leads to the possibility of reusing a property name. Even with an API version change, this change is discouraged because it creates documentation and cognitive challenges. + +#### Property type has changed + +Property `foo was a boolean in v1 but is changed to a string. A client using the existing API version tries to set it as a boolean, but the service will fail since its now expecting a string. So, the API version must be updated. + +#### Property default value has changed + +If a property is optional and the service provides a default value, changing that default requires an updated API version. + +#### Allowed values for an enum have changed +Enum “foo” had allowed values as “val1” and “val2” in v1 of API. Now, the values accepted by the service are “val1”, “val2” and “val3”. The client will fail to de-serialize if “val3” comes back in the response. + +#### API has been removed or renamed + +V1 of API contract supported `PUT /resourceType1/{resourceType1_name}` but the service no longer supports this method. This scenario should follow the proper Azure API deprecation policy and must be done in an updated API version. + +#### Behavior of existing API has changed + +There is a functional change in what the API was doing. This is a complex issue because it sometimes is not an easy option to maintain the old behavior even on an old API version. It also is very confusing to end users even when version is update and documented. Behavior changes need to be well justified and discussed on a case-by-case basis. + +#### Error contracts have changed + +#### Property is made required (from optional) + +If property “foo” was optional in the request body of v1 and now it is required, this should result in an API version change. If not changed, clients relying on the older API version will fail if this property is not passed. + +#### URL format has changed + +Resource parameter names change from `/resourceType1/{resourceType1_name}` to `/resourceType1/{resourceType1_id}`. This will impact code generation. + +#### Resource naming rules should not change + +This could result in failures which would have earlier succeeded. Even if the rules become less strict, clients relying on earlier name constraints to perform local validation will fail. + +### Non-Breaking Changes + +The following changes are considered backwards compatible and hence non-breaking. + +#### Adding new APIs to an existing service + +When a new resource types is added, it does not require API version to be updated for existing types. + +#### Bug fixes to existing API + +Bug fixes to existing API which don’t fall into one of the above categories of breaking changes as described above are fine. From 68fc205342a47a49f3db95eccc2c8c3c2c15975a Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 14 Apr 2020 07:58:38 -0700 Subject: [PATCH 042/729] Fixed markdownlint errors --- azure/Guidelines.md | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 6b4916d9..53c78415 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -26,11 +26,9 @@ All Services **MUST** provide an [OpenAPI Definition] (with [autorest extensions In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services -### URL structure - All services **MUST** expose their service to developers via the following URL pattern: -``` +```text https://.// ``` @@ -56,26 +54,26 @@ In addition to the required format above, services **MAY** also choose to expose The format of the root of the direct endpoint **MUST** be as follows: -``` +```text https://-..azure.net ``` 1. A request is made to the default end point (GET or HEAD). For example: - ``` + ```text GET https://blobstore.azure.net/contoso.com/account1/container1/blob2 ``` 2. That request is returned with the `Content-Location` header set to the direct endpoint. See [RFC2557]: - ``` + ```text 200 OK Content-Location: https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 ``` Or, with the GUID format: - ``` + ```text 200 OK Content-Location: https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 ``` @@ -88,7 +86,7 @@ All Azure APIs **MUST** use explicit versioning. The Microsoft REST API guidelin The Microsoft REST API guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: -``` +```text GET https://blobstore.azure.com/foo.com/acct1/c1/blob2?api-version=1.0 PUT https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 @@ -96,7 +94,7 @@ POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 ### Breaking changes -A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. +A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Azure services **MUST** update the version number of their API whenever there is a change to the API, no matter how small. Customers will "lock the API version" so that their code does not fail when the service introduces new features. They rely on the fact that an API version is a contract with the services that will never change. @@ -115,12 +113,11 @@ At a high level, any change to the contract of an API constitutes a breaking cha * An existing property is removed. * A new property is added to an existing response. * A new required property is added to an existing request. -* A property name is changed. +* A property name is changed (including case changes). * A property type is changed. * The default value of a property is changed. * The allowed values for an enum is changed. * An API is removed. -* An API is removed. * The behavior of an existing API is changed. * The error contract has changed. * A property is made required (from optional) @@ -158,14 +155,14 @@ Azure services **SHOULD** support API version discovery. If they support it: Example request to discover versions (blob storage container list API): -``` +```text OPTIONS /?comp=list HTTP/1.1 host: accountname.blob.core.azure.net ``` Example response: -``` +```text 200 OK api-supported-versions: 2011-08,2012-02,1.1,2.0 api-deprecated-versions: 2009-04,1.0 From ce26bc6056a0bbc5d00563e737763675bf7f5c2c Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 14 Apr 2020 08:02:41 -0700 Subject: [PATCH 043/729] Removing old guidelines --- azure/breaking-changes.md.old | 66 ----------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 azure/breaking-changes.md.old diff --git a/azure/breaking-changes.md.old b/azure/breaking-changes.md.old deleted file mode 100644 index 5f5db488..00000000 --- a/azure/breaking-changes.md.old +++ /dev/null @@ -1,66 +0,0 @@ -A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). - -Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Azure services **MUST** update the version number of their API even when adding optional fields. In fact, servers should be as strict as possible. Ignoring a field can result in the API accepting content that containered a typo or an element at the wrong level of nesting. If this missing field changes the semantics (for example, we have seen cases where security settings were misplaced and ignored, leaving the resources more exposed than intended) this can be a huge and hard to discover error. - -At a high level, changes to the contract of an API constitute a breaking change. Changes that impact backwards compatibility of an API is also considered a breaking change. Anything that would violate the Principle of Least Astonishment is considered a breaking change in Azure. Below are some concrete examples of what constitutes a breaking change. In the below breaking change scenarios, the API version must be changed. - -#### Existing property is removed - -If a property called `foo` that was present in v1 of the API needs to be removed, it must be done in a newer API version. - -#### New property added to response - -If a new property/field is added to the response of an API, the GET-PUT pipeline will be broken. Consider the case where a customer updates the value of a new property "A" from the Azure portal. Another customer does a GET of this resource using the SDK. The SDK will ignore the property since it does not understand it. From the SDK, the customer does a PUT using the model that was returned from the GET. This will overwrite the change made by the first customer from the portal. - -#### New required property added to request - -If a new property is made required in the request body, clients will have no way to set this and the request will fail. - -#### Property name has changed - -Note that this is implied by the requirement that adding/and removing properties are breaking changes, but in some ways worse, since it leads to the possibility of reusing a property name. Even with an API version change, this change is discouraged because it creates documentation and cognitive challenges. - -#### Property type has changed - -Property `foo was a boolean in v1 but is changed to a string. A client using the existing API version tries to set it as a boolean, but the service will fail since its now expecting a string. So, the API version must be updated. - -#### Property default value has changed - -If a property is optional and the service provides a default value, changing that default requires an updated API version. - -#### Allowed values for an enum have changed -Enum “foo” had allowed values as “val1” and “val2” in v1 of API. Now, the values accepted by the service are “val1”, “val2” and “val3”. The client will fail to de-serialize if “val3” comes back in the response. - -#### API has been removed or renamed - -V1 of API contract supported `PUT /resourceType1/{resourceType1_name}` but the service no longer supports this method. This scenario should follow the proper Azure API deprecation policy and must be done in an updated API version. - -#### Behavior of existing API has changed - -There is a functional change in what the API was doing. This is a complex issue because it sometimes is not an easy option to maintain the old behavior even on an old API version. It also is very confusing to end users even when version is update and documented. Behavior changes need to be well justified and discussed on a case-by-case basis. - -#### Error contracts have changed - -#### Property is made required (from optional) - -If property “foo” was optional in the request body of v1 and now it is required, this should result in an API version change. If not changed, clients relying on the older API version will fail if this property is not passed. - -#### URL format has changed - -Resource parameter names change from `/resourceType1/{resourceType1_name}` to `/resourceType1/{resourceType1_id}`. This will impact code generation. - -#### Resource naming rules should not change - -This could result in failures which would have earlier succeeded. Even if the rules become less strict, clients relying on earlier name constraints to perform local validation will fail. - -### Non-Breaking Changes - -The following changes are considered backwards compatible and hence non-breaking. - -#### Adding new APIs to an existing service - -When a new resource types is added, it does not require API version to be updated for existing types. - -#### Bug fixes to existing API - -Bug fixes to existing API which don’t fall into one of the above categories of breaking changes as described above are fine. From fec29b0c58e6dbc70488a9dcc2565ece73f9167e Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 14 Apr 2020 11:37:10 -0700 Subject: [PATCH 044/729] Changed header for breaking changes to more clearly reflect intent --- azure/Guidelines.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 53c78415..c4ddeafa 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -92,12 +92,12 @@ PUT https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 ``` -### Breaking changes - -A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. +### API Changes that require a version change Azure services **MUST** update the version number of their API whenever there is a change to the API, no matter how small. Customers will "lock the API version" so that their code does not fail when the service introduces new features. They rely on the fact that an API version is a contract with the services that will never change. +A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. + There are three reasons why a service may issue a breaking change: 1. To remove a security vulnerability. From c7867fc1f26492fbcbf33d7627da9366aa0f49ec Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Wed, 15 Apr 2020 07:52:03 -0700 Subject: [PATCH 045/729] Added optional property additions --- azure/Guidelines.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index c4ddeafa..bde47b3e 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -128,8 +128,9 @@ At a high level, any change to the contract of an API constitutes a breaking cha Not all changes are breaking. The following changes are considered backwards compatible and hence non-breaking. -* Adding a new API to an existing service. -* Changing a property from required to optional. +* A new API is added. +* An optional property is added to an existing request. +* A property is changed from required to optional. In these cases, a new version number is still required. If using SemVer, it is appropriate to use a minor version change. From de413f19237b1525a8f0501df58ac10efa490275 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 21 Apr 2020 10:42:48 -0700 Subject: [PATCH 046/729] Updates from the 2020-04-21 REST API review board --- azure/2020-04-21-Minutes-Versioning.md | 256 +++++++++++++++++++++++++ azure/Guidelines.md | 54 ++---- 2 files changed, 277 insertions(+), 33 deletions(-) create mode 100644 azure/2020-04-21-Minutes-Versioning.md diff --git a/azure/2020-04-21-Minutes-Versioning.md b/azure/2020-04-21-Minutes-Versioning.md new file mode 100644 index 00000000..ff7f753a --- /dev/null +++ b/azure/2020-04-21-Minutes-Versioning.md @@ -0,0 +1,256 @@ +# Minutes from the REST API Review Board 4/21/2020 + +These notes were captured during the meeting of the REST API Review Board on 4/21/2020. They do not represent a complete conversation. + +## Terminology + +The first discussion was around terminology. There are three levels of changes that need to be discussed: + +1. **Compliance changes** - changes that may be breaking, but have to be done to an existing API due to security or compliance reasons. +2. **Breaking changes** - major changes to an API that will break an SDK user due to code generation. +3. **Evolutionary changes** - minor, additive changes to an API to support new features of the services. + +## When to do a version bump + +All changes (except compliance changes) must version bump. We should be encouraging service teams to use preview versions and release GA versions of the API less frequently. This allows the service to evolve without rapid and expanding versions of the API. + +When using SDKs, we want customers to not break as they move forward. Take, for example, a customer that wants to take advantage of a small additive feature, but moving to the newer version of the API results in breaking changes - fallout that they never considered previously. + +## Code gen + +There was a lot of discussion on the effects of code generation and how the SDK may break even though the API didn't technically have a breaking change. This conversation was tabled - our job is to get the APIs right; Metadata changes (in the Swagger definition) have the potential to cause breaking changes without a similar breaking change in the API. + +Participants noted that tooling is available for detecting breaking changes on the codegen when working in azure-rest-api-specs repo. Perhaps we can productize this? + +## How to reason about changes? + +Jeffrey provided a table that allows service teams to reason about the changes to properties in an API: + +| Change | Input | Output | +|========|=======|========| +| Remove | Major | Major | +| Add optional | Minor | Major | +| Add required | Major | Major | +| Data type change | Major | Major | +| Format change | Major | Major | +| Int widens | Minor | Major | +| Int narrows | Major | Minor | +| Add enum | Minor | Major | +| Remove enum | Major | Major | +| Optional to required | Major | (See add) | +| Required to optional | Minor | Major | + +## Further work + +We need four further meetings (at least) to close on this topic: + +1. SDK / Codegen processes +2. Enforcement +3. API Evolution processes for service teams +4. API versioning for additive changes + +## Chat log + +The following is the chat log (raw) + +[9:13 AM] Darrel Miller + In Azure API Management, we avoided using the term breaking change because it has too much baggage. We chose to use "Version" to indicate changes that a a client "opts-in" to. I.e. you change a version identifier in the URL and a "Revision" where changes are made by the server where the customer gets those changes automatically without doing anything. +​[9:18 AM] David Justice + Gareth can you expand on PUT vs ? +​[9:19 AM] David Justice + Are folks using PATCH instead? +​[9:19 AM] Anne Loomis Thompson + This was my suggested rewrite: [https://github.com/microsoft/api-guidelines/pull/192#discussion_r410769561](https://github.com/microsoft/api-guidelines/pull/192#discussion_r410769561) +​[9:19 AM] Gareth Jones + yes +(1 liked)​ +[9:19 AM] Darrel Miller + Most Graph API use PATCH +​[9:19 AM] Gareth Jones + exactly that +​[9:19 AM] Gareth Jones + From what I've seen PATCH is more dominant broadly too +​[9:20 AM] Johan Stenberg + [https://github.com/johanste/serviceapievolution/blob/503667bb8047b3b0e36765e9769c44a0f2756ae7/guidance.md](https://github.com/johanste/serviceapievolution/blob/503667bb8047b3b0e36765e9769c44a0f2756ae7/guidance.md) +​[9:29 AM] Ryan Sweet + Devil's advocate discussion fulcrum: what would be the most customer friendly stance (absent consideration of the burden on the development teams)? +[9:30 AM] Ryan Sweet + Hypothesis: the majority of customers would prefer to see that any changes to the service or metadata result in a version bump +[9:31 AM] Steve Faulkner + FWIW we frequently do additive changes in Cosmos REST APIs with no version bump. I don't think I've ever seen a customer complain about that. +(2 liked) +[9:33 AM] Jeffrey Richter + **Insert table provided earlier** +[9:43 AM] David Justice + Should we encourage services to introduce features more often in preview versions and less often mint "GA" versions? +(1 liked)​ +[9:43 AM] Steve Faulkner + Jeffrey Richter Would also like to see a row/meeting/examples for behavior changes (ie changes only detectable at runtime) +​[9:44 AM] Mark Cowlishaw + Jeffrey Richter Does minor map to 'forward compatible' changes, where major is essentially non-forward-compatible changes? +​[9:45 AM] Darrel Miller + The Microsoft REST guidelines actually say that major.minor is how services should be versioned [https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#121-versioning-formats](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#121-versioning-formats) The Date based format was intended to group multiple services. +​[9:45 AM] Johan Stenberg + ..and the implied assumption is also that major versions are extremely rare... +[9:45 AM] Gareth Jones + Ryan Sweet hard disagree with your hypothesis. From our experience most customer s would prefer that the majority of change would happen with no version change whatever. +(1 liked)​ +[9:46 AM] David Justice + Seems we'd also do well to deliver features in preview, stacking the changes and iterating on those apis. +​[9:47 AM] David Justice + optional changes, preview stacking and PATCH usage == (laugh) +​[9:47 AM] Jeffrey Richter + My thinking has changed on this recently. I think MINOR should map to a change that doesn't break old api-versions. And MAJOR should map to a change that does break old api-versions. If we can all agree to this, then I think we should apply this thinking to my table. What's there might be right (or close) but I'm open to finer discussion on the cell values. +​[9:48 AM] Gaurav Bhatnagar + optional properties should not require a new api-version. its not ideal, but i think we are in a state where we have to make that a norm and live with it. we need to tell the clients that they have to handle such change. +(1 liked) +[9:48 AM] Jeffrey Richter + Service teams can do minor updates whenever they want (although we may not want every 2 weeks for other reason). Major updates requires a BIG review and discussion as to how to minimize the negative impact to existing customers. +​[9:48 AM] Gaurav Bhatnagar + it slows down +​[9:48 AM] David Justice + Gaurav, if that guidance is paired with the use of PATCH, it works +​[9:49 AM] Gaurav Bhatnagar + yes agreed David +(1 liked)​ +[9:49 AM] Gaurav Bhatnagar + i would go one step further +​[9:50 AM] Jeffrey Richter + Gaurav Bhatnagar: optional properties DO require a new api-version. Otherwise a client can't know if the optional property they want is guaranteed to be there. +​[9:50 AM] Anne Loomis Thompson + My internet just cut out +​[9:52 AM] Gaurav Bhatnagar + Jeffrey Richter and I am arguing against making it require a new api-version. its not practical. it has been a rule since ARM existed but we are in this hole. there are too many challenges enforcing it. and if we cant enforce it, there is not point having that guidance. clients can ignore properties it doesnt understand. +(1 liked) +​[9:52 AM] Mark Cowlishaw + One thing that definitely resonates is including specific, real examples of the changes that fit in each category, and why they constitute major or minor changes +(1 liked)​ +[9:53 AM] David Justice + we should record these +(1 liked) +[9:53 AM] Gaurav Bhatnagar + and there is no way in Azure api-versioning scheme to know when something is minor or something is major change. +​[9:54 AM] Mark Cowlishaw + correct, this stands in for forward-compatible or non-forward compatible changes, I think, and perhaps whether an api-version change is required +​[9:54 AM] David Justice + These conversations would do well to be preserved to help the people who will inevitably be discussing this 5 years from now. +Edited +​[9:54 AM] Mark Cowlishaw + although for now, we are saying api-version changes for everything +​[9:55 AM] Gaurav Bhatnagar + Mark - i would think hard about doing it +​[9:55 AM] Mark Cowlishaw + I am not saying I agree, I am saying that is the current stance +​[9:57 AM] Jeffrey Richter + Gaurav Bhatnagar: I see what you want and see why but I'd prefer if there was a way to enforce it. For SDKs, the optional field/property has to be added to a type in the SDK. How can customer code know whether it can expect the value to be a good value or now? Nullable is possible in some languages but is not idiomatic in other languages. Also, when you say option properties, you need to be clear about optionally being sent TO the service or FROM the service. SDKs might be able to tolerate better the optional being sent TO the service but hard to tolerate the optional being sent FROM the service. +​[9:59 AM] Steve Faulkner + The "api-version changes for everything" is worth at least having a meting about. I can only speak for Cosmos, but sounds like we aren't the only service with issues. Even if it is the current guideline. +​[9:59 AM] Johan Stenberg + On a side note, how will a service behave if the added property does not yet exist on the cloud I'm calling? Reject the call? For example, consider the scenario of adding a property/capability to a service API version that already has been made available on Azure Stack (or in a containerized form) - there is no guarantee that the Azure Stack instance has been updated to support the new property in that case. +(1 liked) +[10:01 AM] Gaurav Bhatnagar + Jeffrey - agree with you on the usecase you mention. Is it an unsolvable problem on the client side? I would like us to explore if there any other options here? How do other platforms solve it? GCP API version guidelines indicate such changes to be safe - [https://cloud.google.com/apis/design/compatibility](https://cloud.google.com/apis/design/compatibility). +​[10:03 AM] Khaled Henidak (KAL) + Context really matter: Adding an optional field might look safe but it isn't +​[10:04 AM] Khaled Henidak (KAL) + consider v1 Person{firstName: string} and v2 `Person{firstName:string, lastName: string}` +​[10:04 AM] Khaled Henidak (KAL) + Now client 1(rev'ed) puts a person {firstname:kal, lastname:microsoft} +​[10:04 AM] Khaled Henidak (KAL) + client2 (not rev'ed) gets Kal and modifies firstname {firstName: Khaled} then puts the data +​[10:05 AM] Khaled Henidak (KAL) + what you will end up is unintented data loss +​[10:05 AM] Khaled Henidak (KAL) + because the client replaced the entire payload with what they think is right +​[10:05 AM] Khaled Henidak (KAL) + which is not +​[10:06 AM] Khaled Henidak (KAL) + You may say let us treat put as patch. i.e. if the client didn't specifiy last name then we will not update it.. +​[10:06 AM] Khaled Henidak (KAL) + But what if the client intent to actually remove LastName value? +[10:06 AM] Mark Cowlishaw + Basically, allowing an wpi-version to change means that api-version is essentially meaningless for regions / containerized/airgapped clouds +​[10:06 AM] Darrel Miller + This is why AtomPub has this clause [https://bitworking.org/projects/atom/rfc5023#rfc.section.9.3](https://bitworking.org/projects/atom/rfc5023#rfc.section.9.3) OData adopted this also. +​[10:07 AM] Khaled Henidak (KAL) + that is a protocol spec. we can force it on our sdks, but can we force it on other clients? +​[10:07 AM] Mark Cowlishaw + that is allowing a change without updating the api-version +​[10:07 AM] Khaled Henidak (KAL) + maybe we should have it as a rule for any azure client +​[10:08 AM] Khaled Henidak (KAL) + That becomes even an issue with create or replace methods that we offer in our sdks. +​[10:08 AM] Khaled Henidak (KAL) + because the intention becomes even less clear +[10:09 AM] David Justice + What if versioning was automated? Say, a service team iterates on vNext in preview, staging the changes they want. At some interval, a new version is cut. A diff of that version occurs and the new version is calculated based on the impact of the changes in the API surface. + + The machinery needed for versioning is a part of the system, not overhead of the service team. Versioning does not slow the team. All versioning is enforced and done uniformly. + +Edited​ +[10:09 AM] Johan Stenberg + I think we need to have a separate discussion on the use of PUT for ARM. +(2 liked)​[10:12 AM] Johan Stenberg + I don't see how a service team can avoid thinking about versioning. There are many things that require the v1 of an API to be designed in a way that will not cause future friction (e.g. should this number be a 32 bit value or a 64 bit value, is a boolean a good type to use etc.). A machinery that only detects versioning issues once they have two versions to compare will leave a lot of people painted into corners. +​[10:12 AM] Johan Stenberg + That doesn't mean that tooling cannot help - but we certainly need versioning guidance for the first version of an API as well - including linters etc. +​[10:12 AM] David Justice + still need to design the api +​[10:13 AM] David Justice + I don't want to take jobs away for doing important things like make a api usable and well thought out. +​[10:13 AM] Khaled Henidak (KAL) + I don't think the issue is just tooling. The issue goes to building know how and maybe provide scafolding. +​[10:14 AM] David Justice + There is friction asking teams to version their services for every little change. That needs to be solved to increase feature velocity. +[10:14 AM] Johan Stenberg + Does it? +​[10:14 AM] Johan Stenberg + For GA versions of API versions? +​[10:15 AM] David Justice + Seems that if automating a process with a heavy burden does not make it easier, then we are doing something wrong. +Edited +​[10:15 AM] Johan Stenberg + How heavy is the burden? +​[10:15 AM] Khaled Henidak (KAL) + I think we are discussion this without the cost. e.g. the cost of doing this and breaking your client and getting CRI storm of extremely difficult buts to debug vs the cost of investing a little into versioning. That btw goes to feature velocity and jedi +​[10:16 AM] Gaurav Bhatnagar + In my mind, there are 3 challenges we need to address as it relates to api-versioning guidance 1. Enforcing (specially for things like adding optional properties, optional read only properties in the response, adding values to enum ) 2. Explosion of API-versions - guidance and enforcing 3. Overhead of adding new api-version for every small change on the service side +​[10:18 AM] Khaled Henidak (KAL) + i think optional properties are an overloaded term in Azure. I have looked at a couple of CRP versions they are riddled with optional properties but i know for a fact that not providing most of these properties will yeild into validation errors +​[10:19 AM] Gaurav Bhatnagar + actually there is a 4th one - 4. How does customer know when a new api-version contains a minor additive change or when it contains a major change +​[10:19 AM] Mark Cowlishaw + Can we use preview versions, or a similar concept, LTS versions to solve the problem for ARM - that is, allow changes in non-LTS versions and have LTS versions with strict guidelines be the only ones ported to other clouds +(1 liked)​ +[10:19 AM] Gaurav Bhatnagar + Khaled - wow +[10:19 AM] Mark Cowlishaw + or would that just force all the changes into non-LTS versions? +​[10:20 AM] Khaled Henidak (KAL) + yup. you know that network profile is marked as optional. I know for a fact that you can not create a vm without network and storage profiles +​[10:20 AM] Johan Stenberg + It would be interesting to me to (re)explore the concept of frozen vs. non-frozen APIs. + + * There is a "recommended" version of an API. This is guaranteed to not change. + * There is a "latest" (but still supported/has SLA etc.) version of the service version where new fields may be added. + * An API is automatically frozen (by necessity) as soon as it is available on multiple "clouds" where deployment happens at potentially different schedules. + +(2 liked)​ +[10:20 AM] Khaled Henidak (KAL) + so there is that as well +​[10:20 AM] Mark Cowlishaw + Khaled Henidak (KAL) Yes, that is common across RPs, as we start to create toollking for more services +​[10:21 AM] Johan Stenberg + > yup. you know that network profile is marked as optional. I know for a fact that you can not create a vm without network and storage profiles + + ...however, you can PUT to an existing VM without providing those properties... +[10:21 AM] Khaled Henidak (KAL) + Johan Stenberg my friend. Optional is optional. +​[10:22 AM] Khaled Henidak (KAL) + you are telling me mr user i don't need these things from you. I can default it for you. that means everytime you write you don't need to provide them +​[10:22 AM] Johan Stenberg + Khaled Henidak (KAL), I wasn't the designer of those APIs! (smile) +​[10:22 AM] Khaled Henidak (KAL) + the concept of optional on update only does not exist +​[10:22 AM] Khaled Henidak (KAL) + LOL diff --git a/azure/Guidelines.md b/azure/Guidelines.md index bde47b3e..fd913183 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -94,45 +94,33 @@ POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 ### API Changes that require a version change -Azure services **MUST** update the version number of their API whenever there is a change to the API, no matter how small. Customers will "lock the API version" so that their code does not fail when the service introduces new features. They rely on the fact that an API version is a contract with the services that will never change. +There are three groups of changes that may happen to an API. -A breaking change is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. +1. Changes made to an **EXISTING** API version due to security or compliance reasons. We shall refer to these types of changes as _Compliance changes_. +2. Changes made to an API that may cause a client making the API call to fail, such as removal of an endpoint or property or changing the format of the body. We refer to these types of changes as _Breaking changes_. +3. Additive changes made to an API that do not cause a client making the API call to fail, such as the addition of a new optional property or a new endpoint. We refer to these types of changes as _Evolutionary changes_. -There are three reasons why a service may issue a breaking change: +With the exception of _Compliance changes_ (which are extremely rare), Azure services **MUST** update the version number of their API whenever there is a change to the API, no matter how small. Customers will "lock the API version" so that their code does not fail when the service introduces new features. They rely on the fact that an API version is a contract with the services that will never change. -1. To remove a security vulnerability. -2. To comply with regulatory requirements. -3. To deprecate a feature of the service. +A _breaking change_ is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Removing an endpoint from an API is always a _breaking change_. Adding a new endpoint is always an _evolutionary change_. Changes to properties may be _evolutionary_ or _breaking_ depending on the type of change and whether the change is to an input parameter or output parameter: -In each case, prior approval of the Azure REST API review board is required. In the case of deprecation, follow the API deprecation policy (below). If the service is using SemVer for versioning, breaking changes constitute a major version change. +| Property change | Input | Output | +|========================|==============|==============| +| Remove a property | Breaking | Breaking | +| Add optional property | Evolutionary | Breaking | +| Add required property | Breaking | Breaking | +| Data type change | Breaking | Breaking | +| Format change | Breaking | Breaking | +| Integer widens | Evolutionary | Breaking | +| Integer narrows | Breaking | Evolutionary | +| Add new value to enum | Evolutionary | Breaking | +| Remove value from enum | Breaking | Breaking | +| Optional to required | Breaking | Breaking | +| Required to optional | Evolutionary | Breaking | -#### Examples of breaking changes +Breaking changes require prior approval of the Azure REST API review board. In the case of deprecation, follow the API deprecation policy (below). If the service is using SemVer for versioning, breaking changes constitute a major version change. -At a high level, any change to the contract of an API constitutes a breaking change. The following is a non-exhaustive list of breaking changes: - -* An existing property is removed. -* A new property is added to an existing response. -* A new required property is added to an existing request. -* A property name is changed (including case changes). -* A property type is changed. -* The default value of a property is changed. -* The allowed values for an enum is changed. -* An API is removed. -* The behavior of an existing API is changed. -* The error contract has changed. -* A property is made required (from optional) -* The URL format is changed. -* Resource naming rules have changed. - -#### Examples of non-breaking changes - -Not all changes are breaking. The following changes are considered backwards compatible and hence non-breaking. - -* A new API is added. -* An optional property is added to an existing request. -* A property is changed from required to optional. - -In these cases, a new version number is still required. If using SemVer, it is appropriate to use a minor version change. +Evolutionary changes do not require prior approval (but still need a version bump). If the service is using SemVer for versioning, evolutionary changes constitute a minor version change. ### Group versioning in Azure and Azure Stack From 39aa2a219a9f82ad9b53b24becd1c77c30e0d39b Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Fri, 24 Apr 2020 10:12:19 -0700 Subject: [PATCH 047/729] Added extensible enum advice --- azure/Guidelines.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index fd913183..3b50e4f6 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -122,6 +122,29 @@ Breaking changes require prior approval of the Azure REST API review board. In t Evolutionary changes do not require prior approval (but still need a version bump). If the service is using SemVer for versioning, evolutionary changes constitute a minor version change. +#### Handling enum additions + +While removing a value from an enum is a breaking change, adding an enum can be handled with an _extensible enum_. An extensible enum is a string value that has been marked with a special marker - setting `modelAsString` to true within an `x-ms-enum` block. For example: + +```json +"createdByType": { + "type": "string", + "description": "The type of identity that created the resource.", + "enum": [ + "User", + "Application", + "ManagedIdentity", + "Key" + ], + "x-ms-enum": { + "name": "createdByType", + "modelAsString": true + } +} +``` + +If you can foresee that an enum will be extended in the future, model the enum as an extensible enum. + ### Group versioning in Azure and Azure Stack Azure Stack allows customers and hosters to deploy their own small versions of Azure and upgrade it at a different pace than Azure. In order to make it possible to write an application or SDK that targets both Azure and Azure Stack, additional versioning policy is necessary. Contact the Azure Stack team for further guidance. From 86d2d55229a588166d86e4fa6fcceef03abfe0da Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 27 Apr 2020 09:04:53 -0700 Subject: [PATCH 048/729] removing minutes --- azure/2020-04-21-Minutes-Versioning.md | 256 ------------------------- 1 file changed, 256 deletions(-) delete mode 100644 azure/2020-04-21-Minutes-Versioning.md diff --git a/azure/2020-04-21-Minutes-Versioning.md b/azure/2020-04-21-Minutes-Versioning.md deleted file mode 100644 index ff7f753a..00000000 --- a/azure/2020-04-21-Minutes-Versioning.md +++ /dev/null @@ -1,256 +0,0 @@ -# Minutes from the REST API Review Board 4/21/2020 - -These notes were captured during the meeting of the REST API Review Board on 4/21/2020. They do not represent a complete conversation. - -## Terminology - -The first discussion was around terminology. There are three levels of changes that need to be discussed: - -1. **Compliance changes** - changes that may be breaking, but have to be done to an existing API due to security or compliance reasons. -2. **Breaking changes** - major changes to an API that will break an SDK user due to code generation. -3. **Evolutionary changes** - minor, additive changes to an API to support new features of the services. - -## When to do a version bump - -All changes (except compliance changes) must version bump. We should be encouraging service teams to use preview versions and release GA versions of the API less frequently. This allows the service to evolve without rapid and expanding versions of the API. - -When using SDKs, we want customers to not break as they move forward. Take, for example, a customer that wants to take advantage of a small additive feature, but moving to the newer version of the API results in breaking changes - fallout that they never considered previously. - -## Code gen - -There was a lot of discussion on the effects of code generation and how the SDK may break even though the API didn't technically have a breaking change. This conversation was tabled - our job is to get the APIs right; Metadata changes (in the Swagger definition) have the potential to cause breaking changes without a similar breaking change in the API. - -Participants noted that tooling is available for detecting breaking changes on the codegen when working in azure-rest-api-specs repo. Perhaps we can productize this? - -## How to reason about changes? - -Jeffrey provided a table that allows service teams to reason about the changes to properties in an API: - -| Change | Input | Output | -|========|=======|========| -| Remove | Major | Major | -| Add optional | Minor | Major | -| Add required | Major | Major | -| Data type change | Major | Major | -| Format change | Major | Major | -| Int widens | Minor | Major | -| Int narrows | Major | Minor | -| Add enum | Minor | Major | -| Remove enum | Major | Major | -| Optional to required | Major | (See add) | -| Required to optional | Minor | Major | - -## Further work - -We need four further meetings (at least) to close on this topic: - -1. SDK / Codegen processes -2. Enforcement -3. API Evolution processes for service teams -4. API versioning for additive changes - -## Chat log - -The following is the chat log (raw) - -[9:13 AM] Darrel Miller - In Azure API Management, we avoided using the term breaking change because it has too much baggage. We chose to use "Version" to indicate changes that a a client "opts-in" to. I.e. you change a version identifier in the URL and a "Revision" where changes are made by the server where the customer gets those changes automatically without doing anything. -​[9:18 AM] David Justice - Gareth can you expand on PUT vs ? -​[9:19 AM] David Justice - Are folks using PATCH instead? -​[9:19 AM] Anne Loomis Thompson - This was my suggested rewrite: [https://github.com/microsoft/api-guidelines/pull/192#discussion_r410769561](https://github.com/microsoft/api-guidelines/pull/192#discussion_r410769561) -​[9:19 AM] Gareth Jones - yes -(1 liked)​ -[9:19 AM] Darrel Miller - Most Graph API use PATCH -​[9:19 AM] Gareth Jones - exactly that -​[9:19 AM] Gareth Jones - From what I've seen PATCH is more dominant broadly too -​[9:20 AM] Johan Stenberg - [https://github.com/johanste/serviceapievolution/blob/503667bb8047b3b0e36765e9769c44a0f2756ae7/guidance.md](https://github.com/johanste/serviceapievolution/blob/503667bb8047b3b0e36765e9769c44a0f2756ae7/guidance.md) -​[9:29 AM] Ryan Sweet - Devil's advocate discussion fulcrum: what would be the most customer friendly stance (absent consideration of the burden on the development teams)? -[9:30 AM] Ryan Sweet - Hypothesis: the majority of customers would prefer to see that any changes to the service or metadata result in a version bump -[9:31 AM] Steve Faulkner - FWIW we frequently do additive changes in Cosmos REST APIs with no version bump. I don't think I've ever seen a customer complain about that. -(2 liked) -[9:33 AM] Jeffrey Richter - **Insert table provided earlier** -[9:43 AM] David Justice - Should we encourage services to introduce features more often in preview versions and less often mint "GA" versions? -(1 liked)​ -[9:43 AM] Steve Faulkner - Jeffrey Richter Would also like to see a row/meeting/examples for behavior changes (ie changes only detectable at runtime) -​[9:44 AM] Mark Cowlishaw - Jeffrey Richter Does minor map to 'forward compatible' changes, where major is essentially non-forward-compatible changes? -​[9:45 AM] Darrel Miller - The Microsoft REST guidelines actually say that major.minor is how services should be versioned [https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#121-versioning-formats](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#121-versioning-formats) The Date based format was intended to group multiple services. -​[9:45 AM] Johan Stenberg - ..and the implied assumption is also that major versions are extremely rare... -[9:45 AM] Gareth Jones - Ryan Sweet hard disagree with your hypothesis. From our experience most customer s would prefer that the majority of change would happen with no version change whatever. -(1 liked)​ -[9:46 AM] David Justice - Seems we'd also do well to deliver features in preview, stacking the changes and iterating on those apis. -​[9:47 AM] David Justice - optional changes, preview stacking and PATCH usage == (laugh) -​[9:47 AM] Jeffrey Richter - My thinking has changed on this recently. I think MINOR should map to a change that doesn't break old api-versions. And MAJOR should map to a change that does break old api-versions. If we can all agree to this, then I think we should apply this thinking to my table. What's there might be right (or close) but I'm open to finer discussion on the cell values. -​[9:48 AM] Gaurav Bhatnagar - optional properties should not require a new api-version. its not ideal, but i think we are in a state where we have to make that a norm and live with it. we need to tell the clients that they have to handle such change. -(1 liked) -[9:48 AM] Jeffrey Richter - Service teams can do minor updates whenever they want (although we may not want every 2 weeks for other reason). Major updates requires a BIG review and discussion as to how to minimize the negative impact to existing customers. -​[9:48 AM] Gaurav Bhatnagar - it slows down -​[9:48 AM] David Justice - Gaurav, if that guidance is paired with the use of PATCH, it works -​[9:49 AM] Gaurav Bhatnagar - yes agreed David -(1 liked)​ -[9:49 AM] Gaurav Bhatnagar - i would go one step further -​[9:50 AM] Jeffrey Richter - Gaurav Bhatnagar: optional properties DO require a new api-version. Otherwise a client can't know if the optional property they want is guaranteed to be there. -​[9:50 AM] Anne Loomis Thompson - My internet just cut out -​[9:52 AM] Gaurav Bhatnagar - Jeffrey Richter and I am arguing against making it require a new api-version. its not practical. it has been a rule since ARM existed but we are in this hole. there are too many challenges enforcing it. and if we cant enforce it, there is not point having that guidance. clients can ignore properties it doesnt understand. -(1 liked) -​[9:52 AM] Mark Cowlishaw - One thing that definitely resonates is including specific, real examples of the changes that fit in each category, and why they constitute major or minor changes -(1 liked)​ -[9:53 AM] David Justice - we should record these -(1 liked) -[9:53 AM] Gaurav Bhatnagar - and there is no way in Azure api-versioning scheme to know when something is minor or something is major change. -​[9:54 AM] Mark Cowlishaw - correct, this stands in for forward-compatible or non-forward compatible changes, I think, and perhaps whether an api-version change is required -​[9:54 AM] David Justice - These conversations would do well to be preserved to help the people who will inevitably be discussing this 5 years from now. -Edited -​[9:54 AM] Mark Cowlishaw - although for now, we are saying api-version changes for everything -​[9:55 AM] Gaurav Bhatnagar - Mark - i would think hard about doing it -​[9:55 AM] Mark Cowlishaw - I am not saying I agree, I am saying that is the current stance -​[9:57 AM] Jeffrey Richter - Gaurav Bhatnagar: I see what you want and see why but I'd prefer if there was a way to enforce it. For SDKs, the optional field/property has to be added to a type in the SDK. How can customer code know whether it can expect the value to be a good value or now? Nullable is possible in some languages but is not idiomatic in other languages. Also, when you say option properties, you need to be clear about optionally being sent TO the service or FROM the service. SDKs might be able to tolerate better the optional being sent TO the service but hard to tolerate the optional being sent FROM the service. -​[9:59 AM] Steve Faulkner - The "api-version changes for everything" is worth at least having a meting about. I can only speak for Cosmos, but sounds like we aren't the only service with issues. Even if it is the current guideline. -​[9:59 AM] Johan Stenberg - On a side note, how will a service behave if the added property does not yet exist on the cloud I'm calling? Reject the call? For example, consider the scenario of adding a property/capability to a service API version that already has been made available on Azure Stack (or in a containerized form) - there is no guarantee that the Azure Stack instance has been updated to support the new property in that case. -(1 liked) -[10:01 AM] Gaurav Bhatnagar - Jeffrey - agree with you on the usecase you mention. Is it an unsolvable problem on the client side? I would like us to explore if there any other options here? How do other platforms solve it? GCP API version guidelines indicate such changes to be safe - [https://cloud.google.com/apis/design/compatibility](https://cloud.google.com/apis/design/compatibility). -​[10:03 AM] Khaled Henidak (KAL) - Context really matter: Adding an optional field might look safe but it isn't -​[10:04 AM] Khaled Henidak (KAL) - consider v1 Person{firstName: string} and v2 `Person{firstName:string, lastName: string}` -​[10:04 AM] Khaled Henidak (KAL) - Now client 1(rev'ed) puts a person {firstname:kal, lastname:microsoft} -​[10:04 AM] Khaled Henidak (KAL) - client2 (not rev'ed) gets Kal and modifies firstname {firstName: Khaled} then puts the data -​[10:05 AM] Khaled Henidak (KAL) - what you will end up is unintented data loss -​[10:05 AM] Khaled Henidak (KAL) - because the client replaced the entire payload with what they think is right -​[10:05 AM] Khaled Henidak (KAL) - which is not -​[10:06 AM] Khaled Henidak (KAL) - You may say let us treat put as patch. i.e. if the client didn't specifiy last name then we will not update it.. -​[10:06 AM] Khaled Henidak (KAL) - But what if the client intent to actually remove LastName value? -[10:06 AM] Mark Cowlishaw - Basically, allowing an wpi-version to change means that api-version is essentially meaningless for regions / containerized/airgapped clouds -​[10:06 AM] Darrel Miller - This is why AtomPub has this clause [https://bitworking.org/projects/atom/rfc5023#rfc.section.9.3](https://bitworking.org/projects/atom/rfc5023#rfc.section.9.3) OData adopted this also. -​[10:07 AM] Khaled Henidak (KAL) - that is a protocol spec. we can force it on our sdks, but can we force it on other clients? -​[10:07 AM] Mark Cowlishaw - that is allowing a change without updating the api-version -​[10:07 AM] Khaled Henidak (KAL) - maybe we should have it as a rule for any azure client -​[10:08 AM] Khaled Henidak (KAL) - That becomes even an issue with create or replace methods that we offer in our sdks. -​[10:08 AM] Khaled Henidak (KAL) - because the intention becomes even less clear -[10:09 AM] David Justice - What if versioning was automated? Say, a service team iterates on vNext in preview, staging the changes they want. At some interval, a new version is cut. A diff of that version occurs and the new version is calculated based on the impact of the changes in the API surface. - - The machinery needed for versioning is a part of the system, not overhead of the service team. Versioning does not slow the team. All versioning is enforced and done uniformly. - -Edited​ -[10:09 AM] Johan Stenberg - I think we need to have a separate discussion on the use of PUT for ARM. -(2 liked)​[10:12 AM] Johan Stenberg - I don't see how a service team can avoid thinking about versioning. There are many things that require the v1 of an API to be designed in a way that will not cause future friction (e.g. should this number be a 32 bit value or a 64 bit value, is a boolean a good type to use etc.). A machinery that only detects versioning issues once they have two versions to compare will leave a lot of people painted into corners. -​[10:12 AM] Johan Stenberg - That doesn't mean that tooling cannot help - but we certainly need versioning guidance for the first version of an API as well - including linters etc. -​[10:12 AM] David Justice - still need to design the api -​[10:13 AM] David Justice - I don't want to take jobs away for doing important things like make a api usable and well thought out. -​[10:13 AM] Khaled Henidak (KAL) - I don't think the issue is just tooling. The issue goes to building know how and maybe provide scafolding. -​[10:14 AM] David Justice - There is friction asking teams to version their services for every little change. That needs to be solved to increase feature velocity. -[10:14 AM] Johan Stenberg - Does it? -​[10:14 AM] Johan Stenberg - For GA versions of API versions? -​[10:15 AM] David Justice - Seems that if automating a process with a heavy burden does not make it easier, then we are doing something wrong. -Edited -​[10:15 AM] Johan Stenberg - How heavy is the burden? -​[10:15 AM] Khaled Henidak (KAL) - I think we are discussion this without the cost. e.g. the cost of doing this and breaking your client and getting CRI storm of extremely difficult buts to debug vs the cost of investing a little into versioning. That btw goes to feature velocity and jedi -​[10:16 AM] Gaurav Bhatnagar - In my mind, there are 3 challenges we need to address as it relates to api-versioning guidance 1. Enforcing (specially for things like adding optional properties, optional read only properties in the response, adding values to enum ) 2. Explosion of API-versions - guidance and enforcing 3. Overhead of adding new api-version for every small change on the service side -​[10:18 AM] Khaled Henidak (KAL) - i think optional properties are an overloaded term in Azure. I have looked at a couple of CRP versions they are riddled with optional properties but i know for a fact that not providing most of these properties will yeild into validation errors -​[10:19 AM] Gaurav Bhatnagar - actually there is a 4th one - 4. How does customer know when a new api-version contains a minor additive change or when it contains a major change -​[10:19 AM] Mark Cowlishaw - Can we use preview versions, or a similar concept, LTS versions to solve the problem for ARM - that is, allow changes in non-LTS versions and have LTS versions with strict guidelines be the only ones ported to other clouds -(1 liked)​ -[10:19 AM] Gaurav Bhatnagar - Khaled - wow -[10:19 AM] Mark Cowlishaw - or would that just force all the changes into non-LTS versions? -​[10:20 AM] Khaled Henidak (KAL) - yup. you know that network profile is marked as optional. I know for a fact that you can not create a vm without network and storage profiles -​[10:20 AM] Johan Stenberg - It would be interesting to me to (re)explore the concept of frozen vs. non-frozen APIs. - - * There is a "recommended" version of an API. This is guaranteed to not change. - * There is a "latest" (but still supported/has SLA etc.) version of the service version where new fields may be added. - * An API is automatically frozen (by necessity) as soon as it is available on multiple "clouds" where deployment happens at potentially different schedules. - -(2 liked)​ -[10:20 AM] Khaled Henidak (KAL) - so there is that as well -​[10:20 AM] Mark Cowlishaw - Khaled Henidak (KAL) Yes, that is common across RPs, as we start to create toollking for more services -​[10:21 AM] Johan Stenberg - > yup. you know that network profile is marked as optional. I know for a fact that you can not create a vm without network and storage profiles - - ...however, you can PUT to an existing VM without providing those properties... -[10:21 AM] Khaled Henidak (KAL) - Johan Stenberg my friend. Optional is optional. -​[10:22 AM] Khaled Henidak (KAL) - you are telling me mr user i don't need these things from you. I can default it for you. that means everytime you write you don't need to provide them -​[10:22 AM] Johan Stenberg - Khaled Henidak (KAL), I wasn't the designer of those APIs! (smile) -​[10:22 AM] Khaled Henidak (KAL) - the concept of optional on update only does not exist -​[10:22 AM] Khaled Henidak (KAL) - LOL From f4c00f2afef074f9ce4ebd0ae220880d2ef14da9 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 28 Apr 2020 12:55:42 -0700 Subject: [PATCH 049/729] Added changes for justification --- azure/Guidelines.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 3b50e4f6..ac479689 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -122,7 +122,30 @@ Breaking changes require prior approval of the Azure REST API review board. In t Evolutionary changes do not require prior approval (but still need a version bump). If the service is using SemVer for versioning, evolutionary changes constitute a minor version change. -#### Handling enum additions +#### Why Azure recommends conservative API versioning + +Azure history is replete with anecdates that directly relate to API versioning. For instance, Cognitive Services unintentionally broke customers by making changes to the API structure without a version bump with updates that they did not think would be breaking changes. These changes led customers to question the stability and maturity of the product and increased the churn rate for the services. + +Even changes that are evolutionary can cause problems. For instance, let's say that a service adds a new feature via a new endpoint in the API. The SDK gets updates to support this new API, but the service does not bump the version number. Since the roll out of the new feature is not atomic, there is a period of time (potentially months long) where the feature is available in some regions but not others. A customer has the potential for attempting to use the feature in two different regions and having it work in one region but not the other, despite the two regions supporting the same version number. This is only made worse when we consider Azure Stack, which can be upwards of a year behind the public cloud offerings. + +**TODO** Include more specific anecdotes on service churn examples. + +There are a few mechanisms that can reduce breaking changes and their effects on our customers. + +#### Use PATCH instead of PUT + +The HTTP PUT verb is an idemopotent apply operation for the API version being used. The [Microsoft API Guidelines already recommend the use of PATCH](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#742-patch) for updates. + +Consider the following sequence: + +* User1 creates a resource with version v2, using a new optional parameter. +* Later, User2 wants to update the resource using unrelated settings. Using version v1, User2 issues a GET, does the changes, and then issues a PUT to replace the resource definition. + +In this case, the optional parameter is lost because of the replace semantics. The optional parameter only exists on API version v2, and not on version v1. + +Service teams SHOULD prefer and recommend PATCH operations for updating resources. + +#### Use extensible enums While removing a value from an enum is a breaking change, adding an enum can be handled with an _extensible enum_. An extensible enum is a string value that has been marked with a special marker - setting `modelAsString` to true within an `x-ms-enum` block. For example: From a225e7147d6c4493d264774ba7b9d7fbdcd93db7 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 28 Apr 2020 12:59:15 -0700 Subject: [PATCH 050/729] Correcting markdownlint issues --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ac479689..81e62307 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -230,7 +230,7 @@ Refer to the Azure deprecation policy for more details. Pre-release and beta APIs are not covered by the normal API deprecation policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. -Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy and process. ** *Customers have suffered downtime because of deprecation of preview APIs* **. +Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy and process. **Customers have suffered downtime because of deprecation of preview APIs**. [1]: https://github.com/microsoft/api-guidelines From a2059f38122b78967791cd0c28d3fb398a1c80f6 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 4 May 2020 12:34:41 -0700 Subject: [PATCH 051/729] Updates based on the Azure Global meeting --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 81e62307..a0a1118f 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -118,7 +118,7 @@ A _breaking change_ is any change in the API that may cause client or service co | Optional to required | Breaking | Breaking | | Required to optional | Evolutionary | Breaking | -Breaking changes require prior approval of the Azure REST API review board. In the case of deprecation, follow the API deprecation policy (below). If the service is using SemVer for versioning, breaking changes constitute a major version change. +Breaking changes require prior approval of the Azure REST API review board and approval through the Azure Global Breaking Change Policy. In the case of deprecation, follow the Azure Global Retirement Policy. If the service is using SemVer for versioning, breaking changes constitute a major version change. Evolutionary changes do not require prior approval (but still need a version bump). If the service is using SemVer for versioning, evolutionary changes constitute a minor version change. From bd7ad407b9319435db5c86ec87b7e37f13327a84 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 4 May 2020 13:04:54 -0700 Subject: [PATCH 052/729] Removed policy section as it is now documented elsewhere --- azure/Guidelines.md | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a0a1118f..ead47270 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -128,8 +128,6 @@ Azure history is replete with anecdates that directly relate to API versioning. Even changes that are evolutionary can cause problems. For instance, let's say that a service adds a new feature via a new endpoint in the API. The SDK gets updates to support this new API, but the service does not bump the version number. Since the roll out of the new feature is not atomic, there is a period of time (potentially months long) where the feature is available in some regions but not others. A customer has the potential for attempting to use the feature in two different regions and having it work in one region but not the other, despite the two regions supporting the same version number. This is only made worse when we consider Azure Stack, which can be upwards of a year behind the public cloud offerings. -**TODO** Include more specific anecdotes on service churn examples. - There are a few mechanisms that can reduce breaking changes and their effects on our customers. #### Use PATCH instead of PUT @@ -210,27 +208,11 @@ Clients that use version discovery are expected to cache version information. Si The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. -## API deprecation policy - -Disabling a runtime REST API that customers are dependent on of course has the potential of breaking their applications or services, perhaps even mission critical services. But inevitably our APIs will become obsolete and the cost of supporting them and operating the servers on which they run will require us to deprecate and shut them down. We have a public policy that describes how we will inform customers that deprecation is coming and help them move their applications off these services and on to their replacements. - -### Policy - -Azure does not have a single SLA for how long we will support all services. However, we have published expectations such as [the Azure Modern Lifecycle Policy][6]. The most relevant section of the document: - -> For products governed by the Modern Lifecycle Policy, Microsoft will provide a minimum of 12 months' notification prior to ending support if no successor product or service is offered—excluding free services or preview releases. - -In practice, we have found this is a bare minimum of how long service endpoints must be supported. Services with any significant usage **SHOULD** expect to run until customers are no longer using them, which can be 10 years or more. - -Service teams **MUST** contact the Azure API review board before communicating the deprecation externally to customers and partners (which starts the 12 month clock). - -Refer to the Azure deprecation policy for more details. - -#### Special case for pre-release and beta APIs +#### Retiring pre-release and beta APIs -Pre-release and beta APIs are not covered by the normal API deprecation policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. +Pre-release and beta APIs are not covered by the Azure Global Retirement and Deprecation Policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. -Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy and process. **Customers have suffered downtime because of deprecation of preview APIs**. +Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy. **Customers have suffered downtime because of deprecation of preview APIs**. [1]: https://github.com/microsoft/api-guidelines From cc784734c5649c93208ec955d749250e7fb69d54 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 4 May 2020 13:07:24 -0700 Subject: [PATCH 053/729] Fixing table formatting --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ead47270..31fdf339 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -105,7 +105,7 @@ With the exception of _Compliance changes_ (which are extremely rare), Azure ser A _breaking change_ is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Removing an endpoint from an API is always a _breaking change_. Adding a new endpoint is always an _evolutionary change_. Changes to properties may be _evolutionary_ or _breaking_ depending on the type of change and whether the change is to an input parameter or output parameter: | Property change | Input | Output | -|========================|==============|==============| +|:-----------------------|:------------:|:------------:| | Remove a property | Breaking | Breaking | | Add optional property | Evolutionary | Breaking | | Add required property | Breaking | Breaking | From 9f9181f3253b907d7d7124da915663004ad2b344 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 4 May 2020 13:15:55 -0700 Subject: [PATCH 054/729] Added link to retirement policy --- azure/Guidelines.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 31fdf339..f7666b95 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -82,6 +82,8 @@ https://-..azure.net All Azure APIs **MUST** use explicit versioning. The Microsoft REST API guidelines offer different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes updates those guidelines to ensure consistency between Azure services across Azure Stack, public Azure, and sovereign clouds. +Retirement of an API version must follow the standard [_Azure Global Retirements and Breaking Changes_][7] policies in effect. + ### Specifying the version in Azure The Microsoft REST API guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: @@ -118,7 +120,7 @@ A _breaking change_ is any change in the API that may cause client or service co | Optional to required | Breaking | Breaking | | Required to optional | Evolutionary | Breaking | -Breaking changes require prior approval of the Azure REST API review board and approval through the Azure Global Breaking Change Policy. In the case of deprecation, follow the Azure Global Retirement Policy. If the service is using SemVer for versioning, breaking changes constitute a major version change. +Breaking changes require prior approval of the Azure REST API review board and approval through the [Azure Global Breaking Change Policy][7]. In the case of deprecation, follow the [Azure Global Retirement Policy][7]. If the service is using SemVer for versioning, breaking changes constitute a major version change. Evolutionary changes do not require prior approval (but still need a version bump). If the service is using SemVer for versioning, evolutionary changes constitute a minor version change. @@ -227,3 +229,4 @@ Though services may set their own deprecation policy for pre-release APIs, they [6]: https://support.microsoft.com/en-us/help/30881 +[7]: http://aka.ms/aprwiki From 6dc7a06082b3696223d50bd69b1f777277798285 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 4 May 2020 14:34:07 -0700 Subject: [PATCH 055/729] Added commentary on changing the API without changing the version --- azure/Guidelines.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index f7666b95..396eb1ec 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -124,6 +124,18 @@ Breaking changes require prior approval of the Azure REST API review board and a Evolutionary changes do not require prior approval (but still need a version bump). If the service is using SemVer for versioning, evolutionary changes constitute a minor version change. +#### Changing the API without changing the version + +There are a limited set of situations where changing the API is permissable without a version bump: + +1. Adding a new (optional) value to an extensible enum. +2. Adding a new (optional) query parameter to adjust the output (for example, adding filter options to a list operation). +3. Adding optional computed read-only output values that are generated based on the new (optional) query parameters. + +Do not add computed output values if the computed value can be calculated from other information in the payload. It unnecessarily expands the payload. + +In these cases, review by the Azure REST API Review Board is required. A version bump is advisable, but not required. + #### Why Azure recommends conservative API versioning Azure history is replete with anecdates that directly relate to API versioning. For instance, Cognitive Services unintentionally broke customers by making changes to the API structure without a version bump with updates that they did not think would be breaking changes. These changes led customers to question the stability and maturity of the product and increased the churn rate for the services. From 1c33376b479014c74e0cf21e8859ea87e4de5c8d Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Mon, 4 May 2020 15:11:45 -0700 Subject: [PATCH 056/729] Fixing header levels --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 396eb1ec..f3a55982 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -222,7 +222,7 @@ Clients that use version discovery are expected to cache version information. Si The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. -#### Retiring pre-release and beta APIs +## Retiring pre-release and beta APIs Pre-release and beta APIs are not covered by the Azure Global Retirement and Deprecation Policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. From 00fbe68ba245cf91007634085e8fc7ed3cc20f1f Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 19 May 2020 12:11:57 -0700 Subject: [PATCH 057/729] Updates from board meeting --- azure/Guidelines.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index f3a55982..f82e78f7 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -126,15 +126,26 @@ Evolutionary changes do not require prior approval (but still need a version bum #### Changing the API without changing the version -There are a limited set of situations where changing the API is permissable without a version bump: +Because the API version represents a contract that a developer can rely on when generating SDKs to communicate with the service, there are a limited set of situations where changing the API is permissable without a version bump. The only changes universally allowed: 1. Adding a new (optional) value to an extensible enum. -2. Adding a new (optional) query parameter to adjust the output (for example, adding filter options to a list operation). -3. Adding optional computed read-only output values that are generated based on the new (optional) query parameters. + +An extensible enum is (in essence) a string. The values of the extensible enum drive intellisense and documentation, but the values are not considered exhaustive. + +If a service is **ONLY** available in the Azure public cloud, then an additional situation can be used to add functionality without changing the version: + +1. Adding a new (optional) query parameter to adjust the output (for example, adding filter options to a list operation). +2. Adding optional computed read-only output values that are generated based on the new (optional) query parameters. + +For example, let's say an image service wants to add bounding-box information to the output of an operation. The service can add a new query parameter `includeBoundingBox=true` and then include the bounding box information within the output only when the new query parameter is specified. A version bump is recommended, but not required. If not changing the API version, the service **MUST** update all data centers before the new query parameter is advertised to customers. + +> **DO NOT** use this mechanism just to get around the version bump. Adding such query parameters results in sub-optimal API designs and should only be used for exceptional circumstances. + +This functionality is only available for single cloud deployments because the API version specifies the contract with the developer. Consider, for example, if such a functionality was included in Azure public cloud and not a sovereign cloud. A developer creating an SDK based on this functionality may see the application work in one cloud but fail when targeting the other despite using the same API version in both cases. For the purposes of this situation, "other clouds" includes Azure Stack and other deployment mechanisms such as containers for on-premise usage. Do not add computed output values if the computed value can be calculated from other information in the payload. It unnecessarily expands the payload. -In these cases, review by the Azure REST API Review Board is required. A version bump is advisable, but not required. +All situations where the API definition is changed (irrespective of whether a version change happens or not) **MUST** be reviewed by the Azure REST API Review Board before release. #### Why Azure recommends conservative API versioning @@ -144,7 +155,7 @@ Even changes that are evolutionary can cause problems. For instance, let's say There are a few mechanisms that can reduce breaking changes and their effects on our customers. -#### Use PATCH instead of PUT +#### Use PATCH instead of PUT for updates The HTTP PUT verb is an idemopotent apply operation for the API version being used. The [Microsoft API Guidelines already recommend the use of PATCH](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#742-patch) for updates. From 95d98e5017b1ced98e15d65799fa4515821a3a4d Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 26 May 2020 08:25:57 -0700 Subject: [PATCH 058/729] Changes per @JeffreyRichter --- azure/Guidelines.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index f82e78f7..b6e6fcca 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -58,7 +58,7 @@ The format of the root of the direct endpoint **MUST** be as follows: https://-..azure.net ``` -1. A request is made to the default end point (GET or HEAD). For example: +1. A request is made to the default endpoint (GET or HEAD). For example: ```text GET https://blobstore.azure.net/contoso.com/account1/container1/blob2 @@ -80,7 +80,7 @@ https://-..azure.net ## Versioning -All Azure APIs **MUST** use explicit versioning. The Microsoft REST API guidelines offer different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes updates those guidelines to ensure consistency between Azure services across Azure Stack, public Azure, and sovereign clouds. +All Azure APIs **MUST** use explicit versioning. The Microsoft REST API guidelines offer different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes updates to those guidelines to ensure consistency between Azure services across Azure Stack, public Azure, and sovereign clouds. Retirement of an API version must follow the standard [_Azure Global Retirements and Breaking Changes_][7] policies in effect. @@ -189,7 +189,7 @@ While removing a value from an enum is a breaking change, adding an enum can be } ``` -If you can foresee that an enum will be extended in the future, model the enum as an extensible enum. +Always model an enum as a string unleess you are positive that the symbol set will **NEVER** change over time. ### Group versioning in Azure and Azure Stack @@ -211,7 +211,7 @@ Azure services **SHOULD** support API version discovery. If they support it: 6. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. 7. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request **SHOULD** return the group versions that are supported across the expanded set. -Example request to discover versions (blob storage container list API): +Example request to discover API versions (blob storage container list API): ```text OPTIONS /?comp=list HTTP/1.1 From 6780d8386eba3973f77a9c61283e39f381b1f8ff Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Fri, 5 Jun 2020 12:28:09 -0700 Subject: [PATCH 059/729] Initial preview-versions (#201) * Initial preview-versions * anecdates -> anecdotes --- azure/Guidelines.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index b6e6fcca..6a0920b2 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -147,9 +147,26 @@ Do not add computed output values if the computed value can be calculated from o All situations where the API definition is changed (irrespective of whether a version change happens or not) **MUST** be reviewed by the Azure REST API Review Board before release. +### Preview Versions + +Preview versions of the API can be indicated by adding the suffix `-preview.X` to the end of the API version, where `X` is an incrementing integer. For example: + +* `2020-05-01-preview.1` +* `1.0-preview.2` + +Preview versions are not treated the same way as release versions. In general, there are two types of previews: + +* **Private** previews are released to a known subset of users. The service team knows how to contact each person within the private preview. There are no restrictions on changes within a private preview, as long as the service team communicates effectively with their users on what changes are made and when they will be made. + +* **Public** previews are released broadly, but contain APIs that may change between previews and may be deleted prior to the final version. The `X` integer (indicating the revision of the preview) must be incremented for all breaking changes (resulting in a new API version). Evolutionary changes may be added as needed, as long as the change is communicated broadly. + +You should follow the axiom "don't surprise your customers" when deciding whether to increment the preview version, and err on the side of incrementing the preview version. + +Preview APIs [follow a different path for deprecation and retirement](https://dev.azure.com/msazure/AzureWiki/_wiki/wikis/AzureWiki.wiki/37683/Retirement-of-Previews), and are not subject to the same guarantees as APIs marked as generally available. Preview APIs have a life-span of not more than 12 months, after which they must be retired. + #### Why Azure recommends conservative API versioning -Azure history is replete with anecdates that directly relate to API versioning. For instance, Cognitive Services unintentionally broke customers by making changes to the API structure without a version bump with updates that they did not think would be breaking changes. These changes led customers to question the stability and maturity of the product and increased the churn rate for the services. +Azure history is replete with anecdotes that directly relate to API versioning. For instance, Cognitive Services unintentionally broke customers by making changes to the API structure without a version bump with updates that they did not think would be breaking changes. These changes led customers to question the stability and maturity of the product and increased the churn rate for the services. Even changes that are evolutionary can cause problems. For instance, let's say that a service adds a new feature via a new endpoint in the API. The SDK gets updates to support this new API, but the service does not bump the version number. Since the roll out of the new feature is not atomic, there is a period of time (potentially months long) where the feature is available in some regions but not others. A customer has the potential for attempting to use the feature in two different regions and having it work in one region but not the other, despite the two regions supporting the same version number. This is only made worse when we consider Azure Stack, which can be upwards of a year behind the public cloud offerings. From 4341737f87742f8322dcfa6d5331bbbef0c878b4 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Wed, 8 Jul 2020 09:39:20 -0700 Subject: [PATCH 060/729] Added initial service v1 guidance --- azure/Guidelines.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 6a0920b2..40acbd18 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -2,8 +2,10 @@ ## History -| Date | Version | Notes | -| 2020-Mar-31 | v3.1 | 1st public release of the Azure REST API Guidelines| +| Date | Version | Notes | +| ----------- | ------- | --------------------------------------------------- | +| 2020-Mar-31 | v3.1 | 1st public release of the Azure REST API Guidelines | +| 2020-Jul-31 | v3.2 | Added service advice for initial versions | ## Introduction @@ -18,6 +20,33 @@ Teams building ARM Resource Providers (RPs) MUST follow the additional guidance ARM RPs are Azure Fundamentals requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. +### Advice for new services + +Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. + +* Think about naming from the context of a **developer experience**. + * Start with the "things" your API manipulates, then think about the operations that a developer needs to do to these "things". + * Avoid the use of generic names like "Object", "Job", "Task", "Operation" (for example - the list is not exhaustive). + * What happens to the names when the focus of the service expands? It may be worth starting with a less generic name to avoid a breaking change later on. +* Use previews to get the shape right. There are different notification, breaking change, and lifetime requirements on preview API versions. + * We recommend a minimum of 2 preview versions prior to your first GA release. + * Your API should be in preview for a minimum of 3 months to gain customer insights on usage. + +Breaking changes are a source of concern during initial review. Without a history, the review process attempts to identify patterns that may result in breaking changes later on. For instance: + +* Return collections with server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. +* Use extensible enumerations unless you are completely sure that the enumeration will never expand. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. +* Implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. +* If your API specified access conditions to another resource, think about how to represent that model polymorphically. For example, you may be using a SQL Azure connection now, but extend to Cosmos DB, Azure Data Lake, or Redis Cache later on. Think about how you can specify that resource in a non-breaking manner. +* Be concerned about data widths of numeric types. Wider data types (e.g. 64-bit vs. 32-bit) are more future-proof. +* Think about how the interface will be represented by an SDK. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. +* Implement (and encourage) the use of PATCH for resource modifications. The PATCH operation should be able to modify any mutable property on the resource. + +Additionally, for management APIs: + +* Follow the advice in the [Azure Resource Manager Wiki][2] (internal only). +* Use [RPaaS](https://armwiki.azurewebsites.net/rpaas/overview.html) (internal only) to implement the Azure Resource Provider. + ## API definition All Services **MUST** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. From 60005e68434e4dc63b518b7a653a256dd9c16ddd Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Wed, 8 Jul 2020 09:47:36 -0700 Subject: [PATCH 061/729] Added additional advice around collections --- azure/Guidelines.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 40acbd18..12c1ee75 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -35,6 +35,8 @@ Developing a new service requires the development of at least 1 (management plan Breaking changes are a source of concern during initial review. Without a history, the review process attempts to identify patterns that may result in breaking changes later on. For instance: * Return collections with server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. +* Think about how the developer can reason about the collection organization. Filtering is a common customer request. +* Prefer allowing the developer to specify the ID of the resource. A developer will commonly want to download a specific resource by name. * Use extensible enumerations unless you are completely sure that the enumeration will never expand. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. * Implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. * If your API specified access conditions to another resource, think about how to represent that model polymorphically. For example, you may be using a SQL Azure connection now, but extend to Cosmos DB, Azure Data Lake, or Redis Cache later on. Think about how you can specify that resource in a non-breaking manner. From 01c749060a02e8957ce8834e1d39d60bf4ee1db4 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 4 Aug 2020 09:17:21 -0700 Subject: [PATCH 062/729] Update azure/Guidelines.md Co-authored-by: tg-msft --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 12c1ee75..f9301443 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -32,7 +32,7 @@ Developing a new service requires the development of at least 1 (management plan * We recommend a minimum of 2 preview versions prior to your first GA release. * Your API should be in preview for a minimum of 3 months to gain customer insights on usage. -Breaking changes are a source of concern during initial review. Without a history, the review process attempts to identify patterns that may result in breaking changes later on. For instance: +Preventing future breaking changes is a source of concern during initial review. Without a history, the review process attempts to identify patterns that may result in breaking changes later on. For instance: * Return collections with server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. * Think about how the developer can reason about the collection organization. Filtering is a common customer request. From 66c31c0f5f29d4fb49f60e967aff1c6597671503 Mon Sep 17 00:00:00 2001 From: Adrian Hall Date: Tue, 4 Aug 2020 10:35:47 -0700 Subject: [PATCH 063/729] Updated from board meeting on 8/4 --- azure/Guidelines.md | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index f9301443..7b79d817 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -22,27 +22,42 @@ ARM RPs are Azure Fundamentals requirement for Azure Services and ARM RP review ### Advice for new services -Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. +Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. + +> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. * Think about naming from the context of a **developer experience**. * Start with the "things" your API manipulates, then think about the operations that a developer needs to do to these "things". + * Keep the verbs present-tense. Avoid the use of past or future tense in most cases. * Avoid the use of generic names like "Object", "Job", "Task", "Operation" (for example - the list is not exhaustive). * What happens to the names when the focus of the service expands? It may be worth starting with a less generic name to avoid a breaking change later on. +* Think about the code that a customer will write both before and after the REST API call. How will a developer use this API in the canonical use case? + * Consider multiple languages, and include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). * Use previews to get the shape right. There are different notification, breaking change, and lifetime requirements on preview API versions. - * We recommend a minimum of 2 preview versions prior to your first GA release. - * Your API should be in preview for a minimum of 3 months to gain customer insights on usage. - -Preventing future breaking changes is a source of concern during initial review. Without a history, the review process attempts to identify patterns that may result in breaking changes later on. For instance: - -* Return collections with server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. -* Think about how the developer can reason about the collection organization. Filtering is a common customer request. -* Prefer allowing the developer to specify the ID of the resource. A developer will commonly want to download a specific resource by name. + * We recommend a minimum of 2 preview versions prior to your first GA release. However, there is no hard rule for previews. + * You should gather feedback from your customers and iterate until the API is useful. Actively solicit feedback from your preview customers. + +Preventing future breaking changes is a source of concern during initial review. Without a history, the review process attempts to identify patterns that may result in breaking changes later on. For instance, think about the following: + +* [Think about idempotency](https://stripe.com/blog/idempotency). In a distributed cloud, each HTTP call must be idempotent. You must be resilient in the face of failure. A developer must rely on idempotency to build fault-tolerant systems. + * The HTTP specification requires that GET, PUT, DELETE, and HEAD be idempotent. + * Prefer allowing the developer to use PUT or PATCH to create a resource with a user-specified name or ID. A developer will commonly want to download a specific resource by name or ID. This provides the developer with an idempotent mechanism for creating resources. +* Collections are a common source of review comments: + * Return collections with server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. + * A collection should return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. + * Think about how the developer can reason about the collection organization. Filtering is a common customer request. +* Avoid polymorphism. An endpoint should work with a single type to avoid problems during SDK creation. Remember that a change to the model is a breaking change. * Use extensible enumerations unless you are completely sure that the enumeration will never expand. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. * Implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. -* If your API specified access conditions to another resource, think about how to represent that model polymorphically. For example, you may be using a SQL Azure connection now, but extend to Cosmos DB, Azure Data Lake, or Redis Cache later on. Think about how you can specify that resource in a non-breaking manner. +* If your API specifies access conditions to another resource: + * Think about how to represent that model polymorphically. For example, you may be using a SQL Azure connection now, but extend to Cosmos DB, Azure Data Lake, or Redis Cache later on. Think about how you can specify that resource in a non-breaking manner. + * Implement managed identity access controls for accessing the other resource. Do not accept connection strings as a method of specifying access permissions. * Be concerned about data widths of numeric types. Wider data types (e.g. 64-bit vs. 32-bit) are more future-proof. * Think about how the interface will be represented by an SDK. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. -* Implement (and encourage) the use of PATCH for resource modifications. The PATCH operation should be able to modify any mutable property on the resource. +* Implement (and encourage) the use of PATCH for resource modifications. + * The PATCH operation should be able to modify any mutable property on the resource. + * Prefer JSON merge-patch ([RFC 7396](https://tools.ietf.org/html/rfc7396)) over JSON patch ([RFC 6902](https://tools.ietf.org/html/rfc6902)) as the accepted data format for PATCH operations. +* Get a security review of your API. You should be especially concerned with PII leakage, GDPR compliance and any other compliance regulations appropriate to your situation. Additionally, for management APIs: From 4c5458340bda707b0b32467e5c04a5899e9cd206 Mon Sep 17 00:00:00 2001 From: sublimemm Date: Sat, 6 Feb 2021 23:33:39 -0500 Subject: [PATCH 064/729] Change per Issue #217 recommendations (#218) * Empty Collection Recommendations based on #217 Service providers are unclear if they should return 404s when collection searches have no results. Per the advice in the issue 217 thread the recommendation is to NOT return a 404 in these cases and instead maintain consistency in their response and signifying no results as _value_ containing an empty array. "We would always say that should return a 200 with an empty collection. The call succeeded, the collection exists. There's just nothing in it. Given we always follow the convention of having the array itself be a 'value' property on a top-level object representing the collection itself, there's also the ability to return collection metadata that is valid regardless of whether there is anything in the collection, e.g. MaxItems." -- Gareth Jones * TOC formatting moving to a new section. 9.10. * typo and lower case property name --- Guidelines.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Guidelines.md b/Guidelines.md index 298fc08f..dc194627 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -84,6 +84,7 @@ This document establishes the guidelines Microsoft REST APIs SHOULD follow so RE - [9.8.2. Client-driven paging](#982-client-driven-paging) - [9.8.3. Additional considerations](#983-additional-considerations) - [9.9. Compound collection operations](#99-compound-collection-operations) + - [9.10. Empty Results](#910-empty-results) - [10. Delta queries](#10-delta-queries) - [10.1. Delta links](#101-delta-links) - [10.2. Entity representation](#102-entity-representation) @@ -1006,6 +1007,27 @@ When these operations are performed together, the evaluation order MUST be: 2. **Sorting**. The potentially filtered list is sorted according to the sort criteria. 3. **Pagination**. The materialized paginated view is presented over the filtered, sorted list. This applies to both server-driven pagination and client-driven pagination. +### 9.10. Empty Results +When a filter is performed on a collection and the result set is empty you MUST respond with a valid response body and a 200 response code. +In this example the filters supplied by the client resulted in a empty result set. +The response body is returned as normal and the _value_ attribute is set to a empty collection. +A client MAY be expecting metadata attributes like _maxItems_ based on the format of your responses to similar calls which produced results. +You SHOULD maintain consistency in your API whenever possible. + +```http +GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + ..., + "maxItems": 0, + "value": [] +} +``` + ## 10. Delta queries Services MAY choose to support delta queries. From 5cc80bd089ed7f369e215c89c44b27916073ab90 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 6 Feb 2021 21:18:15 -0800 Subject: [PATCH 065/729] Added badge for license. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 03837a3a..87b28610 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ They may additionally create documents specific to their team, adding further gu We publish these guidelines here with the aim of fostering dialogue and learning in the API community at large. We further hope that these guidelines may encourage other organizations to create guidelines that are appropriate for them and in turn, if they are able, to publish theirs. +[![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) + ## Code of Conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. From ac13d5bb387f5a6de3b157325edb5922ff56efd2 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sat, 13 Mar 2021 12:18:28 -0500 Subject: [PATCH 066/729] first attempt at moving content --- graph/advanced-patterns/README.md | 6 + .../long-running-operations.md | 336 +++++++++++ .../advanced-patterns/omitting-properties.md | 113 ++++ graph/common-patterns/README.md | 13 + graph/common-patterns/deltas.md | 195 +++++++ graph/common-patterns/webhooks.md | 368 ++++++++++++ graph/gmm/README.md | 6 + graph/gmm/gmm-level-1.md | 88 +++ graph/gmm/odata-conformance.md | 81 +++ graph/guidelines.md | 526 ++++++++++++++++++ graph/modeling-patterns/README.md | 6 + graph/modeling-patterns/adding-subtypes.md | 107 ++++ graph/modeling-patterns/containment.md | 5 + graph/modeling-patterns/entity-complex.md | 62 +++ graph/modeling-patterns/evolvable-enums.md | 107 ++++ graph/modeling-patterns/modeling-variants.md | 75 +++ .../navigation-containment.md | 7 + graph/modeling-patterns/odata-types-primer.md | 6 + graph/modeling-patterns/shared-types.md | 18 + graph/modeling-patterns/type-namespaces.md | 191 +++++++ 20 files changed, 2316 insertions(+) create mode 100644 graph/advanced-patterns/README.md create mode 100644 graph/advanced-patterns/long-running-operations.md create mode 100644 graph/advanced-patterns/omitting-properties.md create mode 100644 graph/common-patterns/README.md create mode 100644 graph/common-patterns/deltas.md create mode 100644 graph/common-patterns/webhooks.md create mode 100644 graph/gmm/README.md create mode 100644 graph/gmm/gmm-level-1.md create mode 100644 graph/gmm/odata-conformance.md create mode 100644 graph/guidelines.md create mode 100644 graph/modeling-patterns/README.md create mode 100644 graph/modeling-patterns/adding-subtypes.md create mode 100644 graph/modeling-patterns/containment.md create mode 100644 graph/modeling-patterns/entity-complex.md create mode 100644 graph/modeling-patterns/evolvable-enums.md create mode 100644 graph/modeling-patterns/modeling-variants.md create mode 100644 graph/modeling-patterns/navigation-containment.md create mode 100644 graph/modeling-patterns/odata-types-primer.md create mode 100644 graph/modeling-patterns/shared-types.md create mode 100644 graph/modeling-patterns/type-namespaces.md diff --git a/graph/advanced-patterns/README.md b/graph/advanced-patterns/README.md new file mode 100644 index 00000000..bae24b0b --- /dev/null +++ b/graph/advanced-patterns/README.md @@ -0,0 +1,6 @@ +--- +title: Advanced API Patterns +owner: mastaffo +--- + +# Coming soon diff --git a/graph/advanced-patterns/long-running-operations.md b/graph/advanced-patterns/long-running-operations.md new file mode 100644 index 00000000..2bb9e5b4 --- /dev/null +++ b/graph/advanced-patterns/long-running-operations.md @@ -0,0 +1,336 @@ +--- +title: Long-running operations +owner: mastaffo +--- + +# Long-running operations + +Long running operations, sometimes called async operations, tend to mean different things to different people. This section sets forth guidance around different types of long running operations, and describes the wire protocols and best practices for these types of operations. + +1. One or more clients MUST be able to monitor and operate on the same resource at the same time. +2. The state of the system SHOULD be discoverable and testable at all times. Clients SHOULD be able to determine the system state even if the operation tracking resource is no longer active. The act of querying the state of a long running operation should itself leverage principles of the web. i.e. well defined resources with uniform interface semantics. Clients MAY issue a GET on some resource to determine the state of a long running operation +3. Long running operations SHOULD work for clients looking to "Fire and Forget" and for clients looking to actively monitor and act upon results. +4. Cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a cancelled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. + +## Resource based long running operations (RELO) + +Resource based modeling is where the status of an operation is encoded in the resource and the wire protocol used is the standard synchronous protocol. In this model state transitions are well defined and goal states are similarly defined. + +_This is the preferred model for long running operations and should be used wherever possible_. Avoiding the complexity and mechanics of the LRO Wire Protocol makes things simpler for our users and tooling chain. + +An example may be a machine reboot, where the operation itself completes synchronously but the GET operation on the virtual machine resource would have a "state: Rebooting", "state: Running" that could be queried at any time. + +This model MAY integrate Push Notifications. + +While most operations are likely to be POST semantics, In addition to POST semantics services MAY support PUT semantics via routing to simplify their APIs. For example, a user that wants to create a database named "db1" could call: + +```http +PUT https://api.contoso.com/v1.0/databases/db1 +``` + +In this scenario the databases segment is processing the PUT operation. + +Services MAY also use the hybrid defined below. + +## Stepwise long running operations + +A stepwise operation is one that takes a long, and often unpredictable, length of time to complete, and doesn't offer state transition modeled in the resource. This section outlines the approach that services should use to expose such long running operations. + +Service MAY expose stepwise operations. + +> Stepwise Long Running Operations are sometimes called "Async" operations. This causes confusion, as it mixes elements of platforms ("Async / await", "promises", "futures") with elements of API operation. This document uses the term "Stepwise Long Running Operation" or often just "Stepwise Operation" to avoid confusion over the word "Async". + +Services MUST perform as much synchronous validation as practical on stepwise requests. Services MUST prioritize returning errors in a synchronous way, with the goal of having only "Valid" operations processed using the long running operation wire protocol. + +For an API that's defined as a Stepwise Long Running Operation the service MUST go through the Stepwise Long Running Operation flow even if the operation can be completed immediately. In other words, APIs must adopt and stick with a LRO pattern and not change patterns based on circumstance. + +### `PUT` + +Services MAY enable PUT requests for entity creation. + +```http +PUT https://api.contoso.com/v1.0/databases/db1 +``` + +In this scenario the _databases_ segment is processing the PUT operation. + +```http +HTTP/1.1 202 Accepted +Location: https://api.contoso.com/v1.0/operations/123 +``` + +For services that need to return a partially created response here, use the hybrid flow described below. + +### `POST` + +Services MAY enable POST requests for entity creation. + +```http +POST https://api.contoso.com/v1.0/databases/ + +{ + "fileName": "someFile.db", + "color": "red" +} + +HTTP/1.1 202 Accepted +Location: https://api.contoso.com/v1.0/operations/123 +``` + +### `POST` hybrid model + +Services MAY respond synchronously to POST requests to collections that create a resource even if the resources aren't fully created when the response is generated. In order to use this pattern, the response MUST include a representation of the incomplete resource and an indication that it is incomplete. + +For example: + +```http +POST https://api.contoso.com/v1.0/databases/ HTTP/1.1 +Host: api.contoso.com +Content-Type: application/json +Accept: application/json + +{ + "fileName": "someFile.db", + "color": "red" +} +``` + +Service response says the database is being created at the URL provided in the Content-Location header, but indicates the request is not completed by including a 202 status code and Location header pointing to an operation resource. The response body includes the incomplete representation of the resource that will eventually exist at the URL in the Content-Location header. + +```http +HTTP/1.1 202 Accepted +Content-Location: https://api.contoso.com/v1.0/databases/db1 +Location: https://api.contoso.com/v1.0/operations/123 + +{ + "databaseName": "db1", + "color": "red", + "Status": "Provisioning", + [ … other fields for "database" …] +} +``` + +### Operations resource + +Services MAY provide an `/operations` resource at the tenant level. + +Services that provide the `/operations` resource MUST provide GET semantics. GET MUST enumerate the set of operations, following standard pagination, sorting, and filtering semantics. The default sort order for this operation MUST be: + +| Primary Sort | Secondary Sort | +| ---------------------- | ----------------------- | +| Not Started Operations | Operation Creation Time | +| Running Operations | Operation Creation Time | +| Completed Operations | Operation Creation Time | + +::: tip +**Note:** that "Completed Operations" is a goal state (see below), and may actually be any of several different states such as "successful", "cancelled", "failed" and so forth. +::: + +### Operation resource + +An operation is a user addressable resource that tracks a stepwise long running operation. Operations MUST support GET semantics. The GET operation against an operation MUST return: +1.The operation resource, it's state, and any extended state relevant to the particular API. +2.200 OK as the response code. + +Services MAY support operation cancellation by exposing DELETE on the operation. If supported DELETE operations MUST be idempotent. + +::: tip +**Note:** From an API design perspective, cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a cancelled operation It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. +::: + +Services that do not support operation cancellation MUST return a 405 Method Not Allowed in the event of a DELETE. + +Operations MUST support the following states: + +1. NotStarted +2. Running +3. Succeeded. Terminal State. +4. Failed. Terminal State. + +Services MAY add additional states, such as "Cancelled" or "Partially Completed". Services that support cancellation MUST sufficiently describe their cancellation such that the state of the system can be accurately determined and any compensating actions may be run. + +Services that support additional states should consider this list of canonical names and avoid creating new names if possible: Cancelling, Cancelled, Aborting, Aborted, Tombstone, Deleting, Deleted. + +An operation MUST contain, and provide in the GET response, the following information: + +1. The timestamp when the operation was created. +2. A timestamp for when the current state was entered. +3. The operation state (notstarted / running / completed). + +Services MAY add additional, API specific, fields into the operation. The operation status JSON returned looks like: + +```json +{ + "createdDateTime": "2015-06-19T12-01-03.45Z", + "lastActionDateTime": "2015-06-19T12-01-03.45Z", + "status": "notstarted | running | succeeded | failed" +} +``` + +### Percent complete + +Sometimes it is impossible for services to know with any accuracy when an operation will complete. Which makes using the Retry-After header problematic. In that case, services MAY include, in the operationStatus JSON, a percent complete field. + +```json +{ + "createdDateTime": "2015-06-19T12-01-03.45Z", + "percentComplete": "50", + "status": "running" +} +``` + +In this example the server has indicated to the client that the long running operation is 50% complete. + +### Target resource location + +For operations that result in, or manipulate, a resource the service MUST include the target resource location in the status upon operation completion. + +```json +{ + "createdDateTime": "2015-06-19T12-01-03.45Z", + "lastActionDateTime": "2015-06-19T12-06-03.0024Z", + "status": "succeeded", + "resourceLocation": "https://api.contoso.com/v1.0/databases/db1" +} +``` + +### Operation tombstones + +Services MAY choose to support tombstoned operations. Services MAY choose to delete tombstones after a service defined period of time. + +### The typical flow, polling + +1. Client invokes a stepwise operation by invoking an action using POST +2. The server MUST indicate the request has been started by responding with a 202 Accepted status code. The response SHOULD include the location header containing a URL that the client should poll for the results after waiting the number of seconds specified in the Retry-After header. +3. Client polls the location until receiving a response that indicates the stepwise long running operation is complete. + +### Example of the typical flow, polling + +Client invokes the restart action: + +```http +POST https://api.contoso.com/v1.0/databases HTTP/1.1 +Accept: application/json + +{ + "fromFile": "myFile.db", + "color": "red" +} +``` + +The server response indicates the request has been created. + +```http +HTTP/1.1 202 Accepted +Location: https://api.contoso.com/v1.0/operations/123 +``` + +Client waits for a period of time then invokes another request to try to get the operation status. + +```http +GET https://api.contoso.com/v1.0/operations/123 +Accept: application/json +``` + +Server responds that results are still not ready and optionally provides a recommendation to wait 30 seconds. + +```http +HTTP/1.1 200 OK +Retry-After: 30 + +{ + "createdDateTime": "2015-06-19T12-01-03.4Z", + "status": "running" +} +``` + +Client waits the recommended 30 seconds and then invokes another request to get the results of the operation. + +```http +GET https://api.contoso.com/v1.0/operations/123 +Accept: application/json +``` + +Server responds with a "status:succeeded" operation that includes the resource location. + +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "createdDateTime": "2015-06-19T12-01-03.45Z", + "lastActionDateTime": "2015-06-19T12-06-03.0024Z", + "status": "succeeded", + "resourceLocation": "https://api.contoso.com/v1.0/databases/db1" +} +``` + +### The typical flow, push notifications + +1. Client invokes a long running operation by invoking an action using POST. The client has a push notification already setup on the parent resource. +2. The service indicates the request has been started by responding with a 202 Accepted status code. The client ignores everything else. +3. Upon completion of the overall operation the service pushes a notification via the subscription on the parent resource. +4. The client retrieves the operation result via the resource URL. + +### Example of the typical flow, push notifications existing subscription + +Client invokes the backup action. The client already has a push notification subscription setup for db1. + +```http +POST https://api.contoso.com/v1.0/databases/db1?backup HTTP/1.1 +Accept: application/json +``` + +The server response indicates the request has been accepted. + +```http +HTTP/1.1 202 Accepted +Location: https://api.contoso.com/v1.0/operations/123 +``` + +The caller ignores all the headers in the return. + +The target URL receives a push notification when the operation is complete. + +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "value": [ + { + "subscriptionId": "1234-5678-1111-2222", + "context": "subscription context that was specified at setup", + "resourceUrl": "https://api.contoso.com/v1.0/databases/db1", + "userId" : "contoso.com/user@contoso.com", + "tenantId" : "contoso.com" + } + ] +} +``` + +### `Retry-After` + +In the examples above the Retry-After header indicates the number of seconds that the client should wait before trying to get the result from the URL identified by the location header. + +The HTTP specification allows the Retry-After header to alternatively specify a HTTP date, so clients should be prepared to handle this as well. + +```http +HTTP/1.1 202 Accepted +Location: http://api.contoso.com/v1.0/operations/123 +Retry-After: 60 +``` + +::: tip +**Note:** The use of the HTTP Date is inconsistent with the use of ISO 8601 Date Format used throughout this document, but is explicitly defined by the HTTP standard in [RFC 7231][rfc-7231-7-1-1-1]. Services SHOULD prefer the integer number of seconds (in decimal) format over the HTTP date format. +::: + +## Retention policy for operation results + +In some situations, the result of a long running operation is not a resource that can be addressed. For example, if you invoke a long running Action that returns a Boolean (rather than a resource). In these situations, the Location header points to a place where the Boolean result can be retrieved. + +Which begs the question: "How long should operation results be retained?" + +A recommended minimum retention time is 24 hours. + +Operations SHOULD transition to "tombstone" for an additional period of time prior to being purged from the system diff --git a/graph/advanced-patterns/omitting-properties.md b/graph/advanced-patterns/omitting-properties.md new file mode 100644 index 00000000..e5b7bc8b --- /dev/null +++ b/graph/advanced-patterns/omitting-properties.md @@ -0,0 +1,113 @@ +--- +title: "RFC: Omitting properties" +owner: piotrci +--- + +# RFC: Omitting properties + +::: danger Review period ends 10/10/2018 + +::: +There are scenarios where the server contains business logic that determines if a property value should be returned, or not, to the client. Even when the client explicitly requests the property, it may be purposefully omitted from the response by the server. This section provides guidance on how the server should explicitly represent omitted properties in the response. + +::: tip +In such scenarios, the property is publicly known and published through the schema. Clients are aware of the property and they can request it, but the server decides to not return it. +::: + +## Scenarios + +These are scenarios existing today where omitting properties is desirable: + +- The property value is only available in tenants that subscribe to a specific level of paid product, such as Azure AD Premium P2 or Microsoft 365 E5. +- The client is calling with permissions that are not sufficient to view the property. For example, `User.ReadBasic.All` allows access to only the basic properties of the `User` entity. +- The client is calling on behalf of a user whose relationship to the target entity allows limited access. For example, a teacher is accessing student data, but the student is not in the teacher's classroom, so limited properties of the student entity should be returned. + +## `omitted` annotation + +Returing `null` values or implicitly hiding properties creates ambiguity and does not promote correct app logic. Instead, when the server decides to omit property values, it should explictly state that properties are omitted in the response. + +When a property is omitted, it should still be returned with a `null` value; this allows client code that expects the property to work seamlessly and makes the handling of the annotations optional. + +In addition to the null property, a corresponding annotation should be included using the following format: + +```json +"{propertyName}@omitted": +{ + "code": "{code}" +} +``` + +- `propertyName` matches the name of the original property. It is followed by the annotation `@omitted`. +- The value is a JSON payload with one property - `code` - whose value is one of the pre-defined "reason codes". This value can be interpreted programatically, and it is also human-readable. + +App code can interpret this portion of the response to pivot its business logic. For example, a developer can create a multi-tenant application that works in all tenants, even ones that never have access to certain property values; the app can detect when values are omitted and react as appropriate. + +### Response example + +An app is getting a resource that is a collection of `sampleEntity`. It selects properties 1 to 3. + +```http +GET +https://graph.microsoft.com/beta/sampleEntities?$select=id,property1,property2,property3 +``` + +Let's assume that in the tenant in which this request is made, `property3` is not available because the tenant does not have the required product license. The response would look as follows: + +```http +{ + "@odata.context": "https://graph.microsoft-ppe.com/v1.0/$metadata#sampleEntities", + "value": [ + { + "id": "guidA", + "property1": "valueA-1", + "property2": "valueA-2", + "property3": null, + "property3@omitted": { + "code": "licensedProductRequired" + } + }, + { + "id": "guidB", + "property1": "valueB-1", + "property2": "valueB-2", + "property3": null, + "property3@omitted": { + "code": "licensedProductRequired" + } + }, + ] +} +``` + +### Code values + +Code values should be well defined and documented. There should be a unique code for each major scenario that is common across the entire Graph API surface. Teams should strive to align their case with more generic global scenarios. + +These are the code values based on existing scenarios exposed through Graph today: + +| Code | Scenario | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------- | +| licensedProductRequired | A licensed product is required in the tenant, or a licene must be assigned to the user whose data is being accessed. | +| limitedPermissions | The app or user permissions used to make the call are insufficient to access the specific property. | +| limitedRole | The role of the app or user in relation to the target entity is insufficient to access the specific property. | + +::: tip +The difference between `limitedPermissions` and `limitedRole` is subtle, but important. The former will result in an omitted property for all entities in a collection. For example, using the `User.ReadBasic.All` permission will omit properties from **all** users returned. The latter may result in properties omitted for only some entities. For example, a teacher reading users in a school may see full properties for students in their classroom, while seeing limited properties for the rest of the students. +::: + +### When to return annotations + +`@omitted` annotations should be returned when a property was in scope for the requests, but the server decided to omit it. For a property to be in scope means either of these two cases: + +- the request included the `$select` parameter that referenced the property +- or, the request did not include `$select` but the property would normally be included as part of the default property set for the entity (note: some workloads include a subset of properties by default, while others include all properties). + +## Callers opt-in to this behavior + +Unless the caller explicitly opts-in into omit annotations, the response should simply ignore the property and not include it in the reponse. Only when the specific header value is included in the request, should the behavior described above kick in. + +The header used to opt-in is as follows: + +```http +TBD - we have not finalized the header +``` diff --git a/graph/common-patterns/README.md b/graph/common-patterns/README.md new file mode 100644 index 00000000..ac6eb011 --- /dev/null +++ b/graph/common-patterns/README.md @@ -0,0 +1,13 @@ +--- +title: Common API Patterns +owner: vibiret +--- + +# Common patterns to implement in your API + +Great APIs provide consistent ways to address common problems. The common problems include: + +- The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph. This is addressed implementing [change notifications (aka webhooks)](./webhooks.md). +- The ability to track changes (pull) occuring in the data exposed by Microsoft Graph. This is addressed implementing [change tracking (aka delta query)](./deltas.md). + +As an API owner you should implement both those patterns to enable new scenarios for your APIs users but also to save COGS (reducing the need for apps to perform continuous polling on your API). diff --git a/graph/common-patterns/deltas.md b/graph/common-patterns/deltas.md new file mode 100644 index 00000000..9399f21a --- /dev/null +++ b/graph/common-patterns/deltas.md @@ -0,0 +1,195 @@ +--- +title: Adding support for delta queries +owner: vibiret +--- + +# Adding support for delta queries + +Delta query enables application to discover newly created, updated, or deleted entities without performing a full read of the target resource with every request. Microsoft Graph applications can use delta query to efficiently synchronize changes with a local data store. For an overview of the general concept, please [refer to the public documentation](https://docs.microsoft.com/en-us/graph/delta-query-overview). + +## Why should you add delta query support for your entities? + +There are different scenarios where customers are looking at syncing data to a separate system and/or tracking changes in a non-lossy way (making sure they are not missing any changes). These scenarios include compliance solutions, DLP solutions, apps that need to support offline usage and many more. +Today, if your API surface does not support delta queries, the only avenue for customers to implement such scenarios is by **continuously query your API surface**. This increases the complexity and cost of such solutions for customers or makes implementing certain scenarios impossible at scale. More importantly, it greatly increases COGS for the Microsoft Graph as well as your API. + +The Microsoft Identity Platform (AAD) has implemented delta query support for a majority of it's entities and will continue to deliver more delta query support in an effort to provide a better experience for customers but also to **decrease COGS**. + +## How to add support for delta queries in your API + +### Create a new API onboarding review item + +Because you'll edit the API schema, you need to go through API review. Go ahead and create [an API review work item](https://microsoftgraph.visualstudio.com/onboarding/_workitems/create/API%20Review). + +> Note: if you are adding a net new API with Delta query support on day one, you can reuse the existing API onboarding review item so long as the delta query support was included in the initial API review + +### Update the public documentation + +Both the API review process and the API schema modification process will require you to provide a link for documentation update before allowing your changes to be added. By creating the public documentation ahead of time, you're making sure you have the required elements ahead of time and won't be blocked. +There are a few places where the reference of a new delta query support must be inserted. + +1. You need to add your resource to the [table of supported resources](https://docs.microsoft.com/en-us/graph/delta-query-overview#supported-resources) +1. You need to state that your resource supports delta query in the abstract, eg [orgContact](https://docs.microsoft.com/en-us/graph/api/resources/orgcontact?view=graph-rest-1.0). (\*) +1. You need to add a delta query support page for the resource in the api reference eg [orgContact delta](https://docs.microsoft.com/en-us/graph/api/orgcontact-delta?view=graph-rest-1.0&tabs=http). (\*) +1. You need to add reference to any page you added in the coresponding Table Of Content. +1. You need to add an entry for each version/entity that supports Delta queries to the [changelog](https://docs.microsoft.com/en-us/graph/changelog), [guidance](../../document/guidelines/changelog.html) + +> \*: These pages are available for beta and v1.0, make sure you update the beta pages during the public preview of change notifications support for your API. Make sure you update v1.0 pages when support ships for general availability. Updates for different versions can be done in different pull request. + +You can see an example of adding delta query support for both v1.0 and beta to the docs [here](https://github.com/microsoftgraph/microsoft-graph-docs/pull/7451). + +Once your documentation pull request is submitted, the PR must be labeled with "Do not merge" until the changes are in place on the service, and then change the label to "Ready to merge". You should keep the link to the pull request at hand, you'll need it for the next steps. + +Should you require assistance with the documentation process, you can contact the [docs V-Team](mailto:MSGraphDocsVteam@microsoft.com). + +### Submit your API for review + +Now that you have pre-requisite items, you are ready to submit an [API review](../../review/final-prep.html). Describe that you are adding support for delta query (function) for your API. + +> Note: if you are adding a net new API with Delta query support on day one, you can reuse the existing API onboarding review so long as the delta query support was included in the initial API review + +### Update the schema metadata + +Once your API review has been completed, you are ready to publish the schema changes to indicate your Entity Type support delta queries. + +For this step you'll need: + +- The link to the API review work item previously created +- The link to the API review pull request previously completed +- The link to the documentation pull request (you can use the same link for docs and changelog changes) + +In your schema file you need to add the new delta function. Here is an example for the orgContact entity. (this needs to be added as a child of the Schema tag) + +```xml + + + + +``` + +In your entity declaration, you need to add an annotation stating that the entity supports delta query. (this needs to be added as a child of the EntityType tag) + +```xml + + + + + +``` + +You can now submit the schema changes following the [guidance](https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Partners/55110/Test-Using-VSTS-Repo). + +> Note: if you want the delta capability to be hidden from publicly available APIs for testing reasons, you can leverage [Privileged identities](https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Partners/55117/Privileged-Api) by setting the `ags:IsHidden="true"` attribute on both the annotation and the function. + +### Provide required information to support + +In order to provide proper support for our customers, the support teams need information you need to provide them with. + +This information will be provided by starting a separate process owned by support called SPOT. To start the process, [create a new intake](https://microsoftspot.azurewebsites.net/Intake). A release manager will then contact you and guide you through the process of collecting and documenting the required information for support teams. + +When creating the SPOT intake, make sure you indicate you are adding delta query support for your workload and set the following fields: + +- Disclosure level: No restrictions +- Release Type: Product/Service/Program +- Release Sub Type: Feature +- Responsible Org: Deployment Services +- Responsible Team: DS C+AI Team + +In relevant links, add the [delta query support wiki](https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/311338/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks) `https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/311338/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks` + +Some of the information that support will require to add it to their internal documentation include: + +- The ICM service and team for escalation +- The owning team (distribution list) + +### Implement query response + +You need to implement a query response to the `/microsoft.graph.delta` (also aliased `/delta`) requests that the Aggregator Gateway Service (AGS, the service immediately behind graph.microsoft.com) will forward to your workload's API. + +> The delta query endpoint should match the following pattern to avoid requiring code changes for request transformation in the AGS: `/{version}/entity/delta`. + +> The delta query endpoint should authorize on the same permissions required to enumerate the entity type. + +The [OData ASP.NET](https://www.nuget.org/packages/Microsoft.AspNet.WebApi.OData/) and [OData ASP.NET core](https://www.nuget.org/packages/Microsoft.AspNetCore.OData) provide base controllers, serialization wrappers and more that help you generate the delta query response as shown [in the public documentation](https://docs.microsoft.com/en-us/odata/webapi/deltafeed_support). + +### Update routing information + +If your delta query implemention does not live on the same service (FQDN) as your entity's API, you need to update the endpoint routing configuration to account for it. For more information, see [Gradual Config Rollout ACIS Operations](https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Partners/40069/Gradual-Config-Rollout-ACIS-Operations). + +You also need to update your workload configuration to indicate your support the delta sync protocol eg: + +```xml + + + + + + + + +``` + +### Handle delta and skip tokens + +#### Skip tokens + +Skip tokens allow you to implement pagination for the response result to avoid returning too many results at once potentially leading to long response times. + +You must return a @odata.nextLink property with the response object if the enumeration contains more elements than requested (when \$top is provided by the customer, or default number of changes to return). + +The nextLink is a string that can contain any information you choose and it should contain a deltaLink that represents the starting point of the initial request as well as information to skip elements that have already been enumerated. + +When a nextLink is returned, you must not return a deltaLink. The nextLink will be encoded and prefixed by the public Microsoft Graph URL automatically by the AGS before being returned to the client. The client will use the public nextLink to: + +1. Know more changes must be enumerated. +1. Query the next changes. + +When querying the the public nextLink, the AGS will decode the nextLink (value of \$skiptoken query parameter) that was provided by the workload and provide the decoded value as a query parameter named `nextLink` to the workload when forwarding the request. + +When all changes are enumerated, a @odata.deltaLink property should be attached to the response object. + +#### Delta tokens + +Delta tokens allow you to watermark the last change seen by client so you can know exactly where the client left off on the change feed. You can then present to the client on their next request the changes that haven't already been seen. Under no circumstances should a client be missing changes between two delta requests (no gaps). + +If a response to a delta query request is returning the last change available at the time, a @odata.deltaLink property must be added to the response object. The deltaLink is a string that can contain any information you choose and it should allow you to precisely identify the last change seen by the client. + +When a deltalink is returned, you must not return a nextLink. It signals to the client that all the current changes have been enumerated and that they should query back, with the deltaLink, at a later time. The deltaLink will be encoded and prefixed by the public Microsoft Graph URL automatically by the AGS before being returned to the client. + +When querying the the public deltaLink, the AGS will decode the deltaLink (value of \$deltatoken query parameter) that was provided by the workload and provide the decoded value as a query parameter named `deltaLink` to the workload when forwarding the request. + +> Delta links and next links should not exceed 10k characters to avoid routing issues at the AGS level. + +### Handle filter, top and select query paremeters + +You should consider how these Odata query parameters may or may not be supported for optimizing the response: + +- **\$select**: you **must** support the select query parameter to client applications to filter which properties they'd like to get in the response. +- **\$filter**: you may support the filter query parameter to allow client applications to filter which objects they'd like to get from the response. +- **\$top**: you may support the top query parameter to allow client applications to customize the number of results they'd like to get per page in the response. (see nextLink) +- **\$expand**: you may support the expand query parameter to allow client applications to get additional linked entities they get in the response. +- **\$oderby**: you may support the orderby query parameter to allow client applications to customize the order of the results they get in the response. +- **\$skip**: you should **not** support the skip query parameter as it's behavior might conflict with with nextLink behavior already madated by delta query. +- **\$count**: you may support the count query parameter to allow clients applications to get the count of items in the response along with the results. +- **\$search**: you may support the search query parameter to allow clients applications to filter which objects they'd like to get from the response. +- **\$format**: you should **not** support the format query parameter as the AGS is doing some data parsing and replacement before returing the response to clients applications and supports limited formats (JSON). + +> If you are building your API using ASP.NET MVC or ASP.NET core MVC you can leverage the OData library to parse and apply oData query paremeters to the delta feed. Make sure you [configure your service pipeline and add the enablequery tag](https://docs.microsoft.com/en-us/odata/webapi/first-odata-api). The library also allows you to [advertise non-support of some query parameters on object properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnet.odata.query.nonfilterableattribute?view=odata-aspnetcore-7.0) via attributes. + +> If you want more control over OData query options, you can additionally leverage the [ODataQueryOptions object](https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#invoking-query-options-directly). + +> Odata query parameters will be added in the encoded delta/skip token provided to the client application so they do not have to add it to each request. They parameters will be decoded and provided to your workload API by the AGS in any subsequent request. Updating the OData query parameters is not supported after an initial delta/skip token has been generated. The parameters stay consistent over time or the client application must restart the synchronization from scratch with the new parameters (querying delta API with no delta/skip token). + +## How to get help + +Should you need any help during your design and implementation, there are a couple of ways you can reach out: + +- [Stackoverflow.com](https://stackoverflow.com): for any question that does not contain confidential, internal or customer related information. Example: questions about ASP.NET core MVC, questions about the OData libraries, etc. +- [Internal Stackoverflow](https://stackoverflow.microsoft.com): for any question that might contain confidential or internal information. Example: how do I configure the routing in AGS to do... +- [Teams: Microsoft Graph > Delta query](https://teams.microsoft.com/l/channel/19%3a32dabdaf736a4c9482fc3d96967b2ea8%40thread.skype/Delta%2520query?groupId=6d279915-6f5d-452c-895b-7f4f82038843&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47): for any question that contains details really specific to your implementation, escalation of questions left unanswered on one of the stackoverflow platforms, ... + +Ask: please refrain from asking questions directly to the engineering team via either emails, Teams chat etc... This does not scale and it doesn't capture the question (and answer) for other people that might have the same question as you. You should always try to ask a question on stack overflow first before reaching out on the Teams channel. + +## End to end testing + +You can use canary and ppe for end to end testing of the implementation as outlined in [the following documentation](https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Partners/55110/Test-Using-VSTS-Repo). +Private previews can be done by creating a _new version_ of Microsoft Graph as outlined in the documentation previously linked. diff --git a/graph/common-patterns/webhooks.md b/graph/common-patterns/webhooks.md new file mode 100644 index 00000000..1411c133 --- /dev/null +++ b/graph/common-patterns/webhooks.md @@ -0,0 +1,368 @@ +--- +title: Adding support for Change Notifications +owner: vibiret +--- + +# Adding support for Change Notifications + +Microsoft Graph Change Notifications enables application to subscribe to resource changes happening accross Microsoft Graph without having to continuously poll the API for changes. Change notifications can be delivered to the subscriber either via webhooks (the subscriber exposes a notification URL) or via Azure Events Hub (first party only at the moment). For an overview of the general concept, please [refer to the public documentation](https://docs.microsoft.com/en-us/graph/webhooks). + +## Why should you add change notifications support for your entities? + +There are different scenarios where customers need to trigger custom logic execution based on changes in Microsoft Graph. These scenarios range from updating UI to reflect changes in the Graph to scenarios that need to analyze data (eg. DLP, compliance, line of business apps...). +Today, if your API surface does not support change notifications, the only avenue for customers to implement such scenarios is by **continuously query your API surface**. This increases the complexity and cost of such solutions for customers or makes implementing certain scenarios impossible at scale. More importantly, it greatly increases COGS for the Microsoft Graph as well as your API. + +## Architectural overview + +![webhooks architecture](../../images/design/webhookarchitecture.svg) + +The diagram above documents a simplified overview of the service components involved in the change notifications infrastructure: + +- **Customer web service**: this service is owned by he customer (or subscriber). It creates and renews subscriptions by calling Microsoft Graph and receives change notifications. +- **Aggregator Service**: this service is the publicly exposed service that customer applications query. (aka Microsoft Graph, AGS) +- **Subscription Service**: this middleware service _acts as a workload_ from the AGS' perspective and _looks like the AGS_ from a workload's perspective. This service is owned by the Microsoft Graph Change Tracking Team and performs the following tasks: + - Receives and responds to any incoming `/subscriptions` request from the AGS. + - Maintains a store of subscriptions. + - Validates the `notificationUrl` during subscription creation. + - Validates the `notificationUrl` performance on regular basis. + - Coordinates subscriptions CRUD operations with worloads by calling workloads APIs. +- **Publisher Service**: this service receives any notification emitted by workloads, batches multiple notifications to the same notification URL, and delivers notifications. This service also handles retrying delivery when the subscriber's notification URl is slow and/or not available. This service is owned by the Microsoft Graph Change Tracking Team. +- **Workload Service**: this service is the target workload's own API. It maintain it's local subscription store, implements an API for subscriptions CRUD operations and emits notifications. + +## How to add support for change notifications for your entity types + +### Update the public documentation + +1. Add your resource to [the table of permissions](https://docs.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-beta&tabs=http#permissions). This needs to be done for [GET](https://docs.microsoft.com/en-us/graph/api/subscription-get-subscriptions?view=graph-rest-beta&tabs=http#permissions), [POST](https://docs.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-beta&tabs=http#permissions), [UPDATE](https://docs.microsoft.com/en-us/graph/api/subscription-update-subscriptions?view=graph-rest-beta&tabs=http#permissions) and [DELETE](https://docs.microsoft.com/en-us/graph/api/subscription-delete?view=graph-rest-beta&tabs=http) (\*). +1. Add the maximum expiration time entry [in the table](https://docs.microsoft.com/en-us/graph/api/resources/subscription?view=graph-rest-beta#maximum-length-of-subscription-per-resource-type) (\*). +1. Add the example [resource paths](https://docs.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-beta&tabs=http#example) (\*). +1. Add the resource in the [overview page](https://docs.microsoft.com/en-us/graph/api/resources/webhooks?view=graph-rest-beta) as well as a [permission](https://docs.microsoft.com/en-us/graph/api/resources/webhooks?view=graph-rest-beta#permissions) (\*). +1. Add an entry into [supported resources](https://docs.microsoft.com/en-us/graph/webhooks#supported-resources) on the concept page (\*\*). +1. Add an entry in the [change log](https://docs.microsoft.com/en-us/graph/changelog), [guidance](../../document/guidelines/changelog.html) + +> \*: These pages are available for beta and v1.0, make sure you update the beta pages during the public preview of change notifications support for your API. Make sure you update v1.0 pages when support ships for general availability. Updates for different versions can be done in different pull request. + +> \*\*: Make sure you include a (preview) next to your resource name during the public preview of change notifications support for your API. Make sure you remove the (preview) once the support for change notifications is generally available. + +### Provide required information to support + +In order to provide proper support for our customers, the support teams need information you need to provide them with. + +This information will be provided by starting a separate process owned by support called SPOT. To start the process, [create a new intake](https://microsoftspot.azurewebsites.net/Intake). A release manager will then contact you and guide you through the process of collecting and documenting the required information for support teams. + +When creating the SPOT intake, make sure you indicate you are adding change notifications support for your workload and set the following fields: + +- Disclosure level: No restrictions +- Release Type: Product/Service/Program +- Release Sub Type: Feature +- Responsible Org: Deployment Services +- Responsible Team: DS C+AI Team + +In relevant links, add the [change notifications support wiki](https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/311338/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks) `https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/311338/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks` + +Some of the information that support will require to add it to their internal documentation include: + +- The ICM service and team for escalation +- The owning team (distribution list) + +### Update the schema metadata + +The subscription service relies on matching the subscription's resource property with Microsoft Graph schema metadata to understand which workload it should relay requests to. This process: + +1. Gets the entity that matches the resource property. +1. Get the `ags:AddressUrl` property of that entity from the schema. +1. Uses the Fully Qualified Domain Name or hostname of the url to determine which adapter to load. +1. Uses the adapter to determine the full URL of the workload's subscriptions API and call it. + +You need to make sure you have: + +1. An entity onboarded to Microsoft Graph and defined in the metadata +1. This entity has an `ags:AddressUrl` property defined. +1. This entity you are the workload that owns the entity (i.e. no `ags:IsMaster` attribute or the attribute is set to `true`). + +> The entity referenced by a subscription may be hidden via `ags:IsHidden` if the entity is not a publicly available entity but needs to provide support for change notifications (first-party only scenarios). + +If your entity does not meet the requirements listed above, you need to update the schema definition of your entity, or add your entity if it is not defined all together. Please refer to the [guidance](https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Partners/55110/Test-Using-VSTS-Repo). + +### Handle subscriptions CRUD operations + +Subscriptions created by applications are maintained in two separate services, you must provide an API that supports CRUD operations for subscriptions and implement an adapter in the Microsoft Graph subscription service that will relay calls to the former API as described below. + +#### The workload's subcription service + +This service is in charge of reflecting any operation on subscription relayed by the subscription service into the workloads data store. The API is usually colocated with the workload's API. The API you are building to implement subscriptions CRUD operations at a workload's level should be as close as possible to the [public-facing API](https://docs.microsoft.com/en-us/graph/api/resources/subscription?view=graph-rest-beta) to limit the need for payload transformations. You should also consider the following points: + +- Requests to GET existing subscriptions are not proxied to the workload, these are served by the Subscription Store service directly. +- When authorizing requests from the Subscription Store service, the actor of the PFT will be the subscription store service, not AGS. +- The Subscription Store service will use your service's DNS name as the resource uri when requesting tokens, so this needs to be registered on your first-party app. +- For POST operations, the Microsoft Graph Subscription Service will substitute the following properties that were provided by the client: + - `notificationUrl`: replaced by the url of the Publisher Service that the workload should use to submit new notifications. + > This new URL is region specific allowing the workload to send notifications to the publisher in the corresponding region. Should the URL change for any reason, the Subscription Service will delete and re-create subscriptions for the workload byt calling the workload's subscription service. + - `clientState`: replaced by a randomly generated one in order to protect the customer's service. + +> The subscription and publisher services will also replace the subscription id provided by the workload with a unified subscription id generated by the subscription service during the subscription creation. + +#### The Microsoft Graph Subscription Service + +This service is in charge of maintaining the integrity of subscriptions accross internal services (Microsoft Graph and workloads) and coordinates calls to workloads stores to do so. For the subscription store to be aware of new resources that can be subscribed to and start relaying CRUD operations to the workload's, you need to implement an adapter in the **Subscription Service**: + +- You can see a sample of such implementation [on our repository](https://dev.azure.com/msazure/One/_git/AD-AGSChangeTracking/commit/39038e51f9f9c57481e21d32c7b3243920de9163). +- You must specify your workload's application id in the Subscription Publisher service's configuration for tokens to be accepted. +- When submitting the pull request: + 1. Share workload contacts: dev owner alias, dev group alias, and ICM service and team. + 1. Assign members of aadctdevs as reviewers. + +To get started with the repository: + +1. Fork the [AD-AGSChangeTracking](https://dev.azure.com/msazure/One/_git/AD-AGSChangeTracking) repository (you only need the master branch, More instructions on fork can be found [here](https://dev.azure.com/msazure/Forks)). +1. Clone your fork to your local machine. +1. Open a shell as admin and navigate to the newly cloned repository. +1. Run `init.cmd`. +1. Run `init.cmd -createshortcut`, this will create a shortcurt on your desktop. +1. Open the shortcut as an administrator. +1. Navigate to `/src/dev/Notifications` (cd). +1. Run `vsmsbuild dirs.proj` to open the projects in Visual Studio. + +> This repository is built on top of coreXT practices, for more information on how to get started, please refer to the [documentation](https://microsoft.sharepoint.com/teams/corext/LivingDocs/Home.aspx) + +To get the tests running locally: + +1. Request the Graph Subscription team to grant you permissions to the `graphwebhooks-Test` Azure KeyVault. (see [How to get help](#how-to-get-help) to request it on Teams.) +1. Update the default processor architecture to x64: + + 1. In Visual Studio select `Test Settings`. + 1. Then select `Default Processor Architecture`. + 1. Then select `X64`. + + ![Image](../../images/design/webhooks-vs.png) + +> 'build' must be run from the src/dev/notifications folder. Any test failures must be re-run in visual studio. A couple tests fail intermittently, running those multiple times should result in all passing. Stylecop failures must be addressed. + +> Visual studio 2019 is required. Open the project: src/dev/Notifications> vsmsbuild dirs.proj + +#### OData query parameters + +By default the subscription service will forward OData query paremeters in the resource property. It is up to the workload to decide whether they want to support those or not. + +If the workload decides not to support OData query parameters, it should return a valid OData error that explicitly calls out the fact that OData query parameters are not supported for that resource. The subscription service will respond to the initial request with the provided error. + +If the workload decides to support OData query parameters, it should implement those: + +- **\$filter**: if the entity emitting the filter, a notification should be emitted, if not not notification should be emitted. eg for messages `$filter=IsRead eq 'true'` should only emit notifications when the `IsRead` property of the message is `true`. If no filter is provided, all notifcations should be emitted given they match other conditions (changeType, ...). +- **\$select**: if the change is on a property included in the select statement, a notification should be emitted. If the change is not on a property included no notification should be emitted. If no select is provided, all notifcations should be emitted given they match other conditions (changeType, ...). +- **\$top**: not supported in this context. +- **\$orderby**: not supported in this context. +- **\$expand**: not supported in this context. +- **\$count**: not supported in this context. +- **\$format**: not supported in this context. +- **\$search**: not supported in this context. +- **\$skip**: not supported in this context. + +### Send notifications to the publisher service + +You must send any notification to the publisher service's using the `notificationUrl` (POST) that was provided by the Microsoft Graph Subscription Service during the subscription creation. + +The notification Url will require an `Authorization` request header with the following value `Bearer ` where the bearer token is an app-only JWT obtained from EvoSTS for the Microsoft Graph Change Tracking App Id as audience (0bf30f3b-4a52-48df-9a82-234910c4a086). + +> Note: you need to app your app id in the publisher configuration as outlined in the Pull Request sample provided in [The Microsoft Graph Subscription Service](#the-microsoft-graph-subscription-service) section. + +Here is an example payload publishing one notification. + +```json +{ + "Value": [ + { + "@odata.type": "#Microsoft.OutlookServices.Notification", + "Id": null, + "SubscriptionExpirationDateTime": "2019-02-14T23:56:30.1307708Z", + "SequenceNumber": 21, + "ChangeType": "created", + "subscriptionId": "MTE1MTVlYTktMjVkZS00MjY3LWI1YzYtMjg0NzliZmRhYWQ2", + "resource": "https://outlook.office365.com/api/beta/Users('userId@tenantId')/Events('')", + "clientState": "WebhooksSubscriptionState1", + "resourceData": { + "Id": "", + "@odata.etag": "", + "@odata.id": "https://outlook.office365.com/api/beta/Users('userId@tenantId')/Events('')", + "@odata.type": "#Microsoft.OutlookServices.Event", + "OtherResourceData": "some other resource data", + "OtherResourceData2": "some other resource data 2" + } + } + ] +} +``` + +| Property Path | Description | Required | Type | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------ | +| @odata.type | OData Type of the notification as represented on the workload | Yes | String | +| Id | Unique Id for notification | No | String | +| SubscriptionExpirationDateTime | Expiration Date and time of the subscription | Yes | String | +| SequenceNumber | Can be used to allow subscribers to re-order notificiations when they receive them. | No | Number | +| ChangeType | Change type of the resource represented by the notification (created,updated,deleted) | Yes | String | +| SubscriptionId | Internal subscription Id as used by the workload, the Publisher Service will translate it to a public subscription Id for the subscriber | Yes | String | +| Resource | Resource URL on Microsoft Graph. Only exception for Exchange where the Publisher Service translates the value. | Yes | String | +| ClientState | Client state associated with the subscription | Yes | String | +| resourceData.id | Resource Id on Microsoft Graph. | Yes | String | +| resourceData.@odata.id | Resource URL on Microsoft Graph. Only exception for Exchange where the Publisher Service translates the value. | Yes | String | +| resourceData.@odata.etag | Opaque string representing the state of the resource as specified [here](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-odata/c4d715eb-10f6-47fa-9ccc-2ebf926558a6) | No | String | +| resourceData.@odata.type | Resource type as represented on Microsoft Graph. Only exception for Exchange. | Yes | String | +| resourceData.OtherResourceData | Additional properties to be attached. Should not contain customer data unless you are implementing rich notifications (see below) | No | Any | + +> The maximum number of notifications that can be sent at once is 100. Notifications for different subscriptions or tenants can be batched in the same call to the publisher service. + +### Lifecycle notifications (preview)(optional) + +Lifecycle notifications allow subscribers to get additional notifications whenever a change impacting the subscription itself occurs. When receiving a lifecycle notification, subscribers can choose to try to auto-remediate the issue and/or let the end user know that an action is required from them. This feature is currently in public preview and you can learn more about it's usage in the [public documentation](https://docs.microsoft.com/en-us/graph/webhooks-outlook-authz). + +You should always send lifecycle notifications if you choose to implement them. Subscribers will receive them only if they provide an additional `lifecycleNotificationUrl` during subscription creation (POST). + +Lifecycle notifications must be delivered to the `notificationUrl`. They do not include any resource data and must include a `changeType` property (string) for which the valid values are: + +- `missed`: notifications have not been sent to the subscriber because of a resource change, the subscriber should get the latest version of the resource from Microsoft Graph. +- `subscriptionRemoved`: the subscription has been deleted by the workload either because the resource itself has been deleted (eg a user left the company, the corresponding mailbox has been archived and the subscriptions to the mailbox have been deleted) or because of compliance issues (user's password reset, device out of compliance...). The subscriber should try to get the latest version of the resource from Microsoft Graph and if it still exists, subscribe again. The publisher service will delete the subscription from the subscription service automatically upon recenption of such notification. +- `reauthorizationRequired`: the subscription notifications are still delivered to the publisher service by the workload, but the publisher service is holding them until the subscriber completes an [authorization challenge](https://docs.microsoft.com/en-us/graph/webhooks-with-resource-data#authorization-challenge-flow). If the subscriber fails to complete the challenge before the subscription's expiration time, notifications are dropped and the subscription is deleted. If the subscriber completes the authorization challenge in time, pending notifications are delivered. This is automatically generated by the publisher service to ensure that subscribers are still in compliance with access policies when delivering rich notifications (see below). + +> If the list of event types above does not cover your scenario, please let us know (see How to get help), the specification and public documentation allows for introducing new event types. The additional values cannot be any of `created`, `updated` or `deleted` as those values are reserved for classic notifications. + +> The `changeType` property will be renamed to `lifecycleEvent` by the publisher service before being delivered to subscribers so it matches the publicly documented schema. + +## Rich notifications (preview) + +Rich notifications, publicly known as [change notifications with resource data](https://docs.microsoft.com/en-us/graph/webhooks-with-resource-data), build on the fundations layed by change notifications. The major difference between rich notifications and basic notifications being that rich notifications include changed data with the notification delivered to the subscriber. This allows subscribers to understand change context without having to query Microsoft Graph saving them implementation efforts, latency and also saving us COGS. All implementation details provided by basic notifications still apply to rich notifications. + +### Handling rich notifications subscription CRUD requests + +Rich notification subscriptions are created the same way as basic notifications subscriptions. In addition to properties documented in the [Handle subscriptions CRUD operations](#handle-subscriptions-CRUD-operations) section, you'll receive the property `includeResourceData` set to `true` as an indicator that the subscriber wants to receive data with the notifications. If the resource does not support sending data, you must return an error. + +> Note: if you decide to encrypt the resource data before sending it to the publisher service (see below), you will also receive the following additional properties: `encryptionCertificate` which is a base64 encoded certificate public key, `encryptionCertificateId` which is a subscriber-defined identifier for the certificate and must be attached with the encrypted content. + +> Note: if you decide to encrypt the resource data before sending it to the publisher service (see below), you also need to handle `encryptionCertificate` and `encryptionCertificateId` updates on PATCH requests. + +### Encrypting changes notifications with resources payload + +The changed data attached to the notification is always encrypted when delivered to the subscriber. This ensures that under any circumstances the data will not be compromised or tampered with. + +Workloads have two main choices when it comes to encrypting the data: + +- You can delegate the encryption work to the publisher service, simply add any data property to the resourceData object. The publisher will take any property that is not whitelisted (id, odata.type, odata.id), remove them from the resourceData object and create the encrypted content with it. +- You can implement the encryption of the content yourself before submitting the notification to the publisher service. This is especially usefull when you must respect data privacy and/or residency standards as it ensures that nobody else besides the workload and the subscriber can read the data. + +The choice should be carefully considered ahead of time as there's no migration plan in place today that supports changing "who encrypts the payload" once notifications start being sent. + +The encryption boundary is determined by the `RichNotificationsPayloadEncryptionEnabledForWorkloads` setting in the publisher service configuration. When your workload name is in the list of values, the publisher service will encrypt any data attached with the notification. Otherwise, the publisher service expects data to be encrypted prior to receiving the notification from the workload. + +#### Encrypting resource data on workload + +Should you choose to encrypt the payload, we provide a [utility class](https://msazure.visualstudio.com/One/_git/AD-AGSChangeTracking?path=%2Fsrc%2Fdev%2FNotifications%2FNotifications.Azure%2FEncryptor%2FEncryptor.cs&version=GBmaster) that helps you encrypt the content, the key and generate the signature. To get this library add the `Microsoft.Online.AggregatorService.Encryptor` from our [nuget feed](https://msazure.visualstudio.com/One/_packaging?_a=feed&feed=Official). + +> Note: the library is only available for dotnet standard today. + +The library will return 3 out of 5 required properties to add to the `encryptedContent` object: + +- `data`: the encrypted (using the single-use generated symetric key) and base64 encoded representation of the JSON value you provided as input. +- `dataSignature`: the HMAC-SHA256 the digital signature of the `data` value. +- `dataKey`: the encrypted (using the subscriber's provided public key) and base64 encoded single-use generated symetric key to be used to validate signature and decrypt the `data`. + +You are also required to add the following properties to the `encryptedContent` object before attaching it to the notification object: + +- `encryptionCertificateId`: the subscriber generated identified for the certificate provided during the subscription creation. +- `encryptionCertificateThumbprint`: the digital thumbprint of the certificate used for encryption of the single-use generated symetric key. + +> The data, before it's encrypted, must be a JSON object with the same schema public schema of the entity the notification is emitted for. If any **\$select** query parameter is included with the resource property of the subscription object, only properties included in the select statement should be included. For more informations refer to [OData query parameters](#odata-query-parameters). + +Here is an example payload publishing one notification which contains encrypted content. + +```json +{ + "Value": [ + { + "subscriptionId": "c9101526-927a-4eec-95dd-f45683a97c6d", + "changeType": "Created", + "@odata.type": "#Microsoft.Graph.ChatMessage", + "clientState": "ClientSecret", + "subscriptionExpirationDateTime": "2018-05-20T18:23:45.9356913+00:00", + "resource": "teams('7434c182-5c07-418a-8116-5945d043b179')/channels('19:8da7bc05bc4e4e5c9b75b2cfe65124dc@thread.skype')/messages('1540929866182')/replies('1540929866243')", + "resourceData": { + "id": "1532471292816", + "@odata.type": "#Microsoft.Graph.ChatMessage", + "@odata.id": "teams('7434c182-5c07-418a-8116-5945d043b179')/channels('19:8da7bc05bc4e4e5c9b75b2cfe65124dc@thread.skype')/messages('1540929866182')/replies('1540929866243')" + }, + "EncryptedContent": { + "data": "lSI1f79", + "dataSignature": "", + "dataKey": "Xm6Ec/6v", + "encryptionCertificateId": "c4f2788d-914a-4494-903a-57522477a9b0", + "encryptionCertificateThumbprint": "78B664778B44CAECDFB11270E5549C1AD34AA0D9" + } + } + ] +} +``` + +### Sending validation tokens + +Because subscribers do not need anymore to query Microsoft Graph back upon receiving a rich notifications, and because they are exposing a publicly available URL, it is even more important for them to be able to validate the origin of notifications. It allows subscribers to avoid executing code on requests coming from potentially rogue actors. When delivering rich notifications the publisher subscriber will include an array of validation tokens on the notifications collection object. These tokens are JWTs valid for the subscribing application. **No action is required from the workload, they are added automatically.** + +## Additional information + +### Throttling behavior for subscriptions requests + +As requests for subscriptions are transiting through the subscription service before coming to the workload, this might bypass any throttling configuration you might have setup at the AGS level to enforce for the rest of your API surface. + +The subscription service defines throttling rules at the AGS level that will be enforced, which means the subscription service itself will not make more requests than it has defined at the AGS level to the workload. The latest values can be viewed [here](https://microsoftgraph.visualstudio.com/onboarding/_git/AGS-OnboardingAutomationPipeline?path=%2Fgraphags_Public.xml&version=GBThrottlingService&_a=contents) (search for throttle.aad.subscriptionservice) : + +- POST, PUT, DELETE, PATCH - per appid, 2000 requests in 20 sec period. +- POST, PUT, DELETE, PATCH - per tenant per appid, 1000 requests in a 20 sec period. +- All other HTTP methods - per appid, 10000 requests in a 20 sec period. +- All other HTTP methods - per tenant, per appid, 5000 requests in a 20 sec period. + +### End to end testing + +#### Local development flow + +If you are building the subcriptions CRUD API in your workload and you want to validate that it respects the expected contract on your local dev machine, you can leverage unit tests to perform such validation. : + +- Navigate to the `src/dev/Notifications/ExtendedTests/ExtendedTests.EndToEnd/E2ETests.cs` class on your local copy of the service repository. +- Update the `ExchangeEndpointUrl` property value by the local url of the API you are developping ([ref](https://dev.azure.com/msazure/One/_git/AD-AGSChangeTracking?path=%2Fsrc%2Fdev%2FNotifications%2FExtendedTests%2FExtendedTests.EndToEnd%2FE2ETests.cs&version=GBmaster&line=127&lineEnd=127&lineStartColumn=30&lineEndColumn=49&lineStyle=plain)). +- Run the tests. + +> These unit tests are designed to work with mocked APIs and will send invalid access tokens, you should disable authorization for the subscriptions part of your API. + +#### Service End-to-end testing + +You can test the end to end scenario (creating a subscription, generating notifications, receiving notifications) in PPE. Once your code changes have been deployed on the workloads side and your pull request to the subscription/publisher service has been merged, you can request the on call engineer to deploy your build of the subscription/publisher service to PPE. The contact of the on call engineer can be found on [our ICM board](https://portal.microsofticm.com/imp/v3/oncall/current?serviceId=20330&teamIds=36748&scheduleType=timeline&shiftType=current&viewType=1). + +You can also leverage [our public sample](https://github.com/microsoftgraph/aspnetcore-webhooks-sample) as a dummy service that will create and receive notifications. + +### First-Party only notifications + +Microsoft Graph change notifications provides support for notifications available only for first party applications. +The following table lists the resources availabel for susbcription: + +| Resource | Description | +| ---------------------- | ----------------------------------------------------------------------------- | +| AllTenantAssignedPlans | Triggers a notification whenever plans assignement are updated on any tenant. | +| AllTenantTenantDelete | Triggers a notification whenever a tenant gets deleted. | +| AllTenantUserDelete | Triggers a notification whenever a user gets deleted on any tenant. | + +#### How to create a first party subscription + +1. [Create a subscription](https://docs.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-1.0&tabs=http) on the `users` resource with your first party application. +1. [Email the team](mailto:AADCTdevs@microsoft.com?body=Please%20fill%20in%20the%20following%20details:%0A-%20Resource:%20%0A-%20Environment:%20%0A-%20App%20ID:%0A-%20Subscription%20ID:%20&subject=Request%20for%20first%20party%20subscription) providing the following details: + - Resource (from the table above) + - Environment (PROD/public, PPE/public, Mooncake, US gov...) + - App Id + - Subscription ID + +> **Note:** Due to the high volume of notifications you might receive, we recommend you leverage [Azure Event Hubs delivery](https://github.com/microsoftgraph/microsoft-graph-docs/pull/7462) instead of webhook delivery for the notifications. + +## How to get help + +Should you need any help during your design and implementation, there are a couple of ways you can reach out: + +- [Stackoverflow.com](https://stackoverflow.com): for any question that does not contain confidential, internal or customer related information. Example: questions about ASP.NET core MVC, questions about the OData libraries, etc. +- [Internal Stackoverflow](https://stackoverflow.microsoft.com): for any question that might contain confidential or internal information. Example: how do I configure the routing in AGS to do... +- [Teams: Microsoft Graph > Webhooks](https://teams.microsoft.com/l/channel/19%3a4139ee2d50a54a14a75365d505d27364%40thread.skype/Webhooks?groupId=6d279915-6f5d-452c-895b-7f4f82038843&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47): for any question that contains details really specific to your implementation, escalation of questions left unanswered on one of the stackoverflow platforms, ... + +Ask: please refrain from asking questions directly to the engineering team via either emails, Teams chat etc... This does not scale and it doesn't capture the question (and answer) for other people that might have the same question as you. You should always try to ask a question on stack overflow first before reaching out on the Teams channel. diff --git a/graph/gmm/README.md b/graph/gmm/README.md new file mode 100644 index 00000000..ce1f502d --- /dev/null +++ b/graph/gmm/README.md @@ -0,0 +1,6 @@ +--- +title: Design rules +owner: mastaffo +--- + +# Coming soon diff --git a/graph/gmm/gmm-level-1.md b/graph/gmm/gmm-level-1.md new file mode 100644 index 00000000..00f5c249 --- /dev/null +++ b/graph/gmm/gmm-level-1.md @@ -0,0 +1,88 @@ +--- +title: GMM level 1 +owner: mastaffo +--- + +# GMM level 1 + +## Naming + +| Id | Name | Severity | +| ----------------------- | ---------------------------------------------------------------------------------------------- | -------- | +| N0001 | ✔ DO use `lowerCamelCase` for _all_ names. | Error | +| N0002 | ✔ DO use singular nouns for type names and properties with cardinality `1`. | Error | +| N0003 | ✔ DO use plural nouns for collections and properties with cardinality `*`. | Error | +| N0004 | ✖ AVOID using brand names in type or property names. | Error | +| N0005 | ✖ AVOID using acronyms or abbreviations unless the abbreviation is extremely well known. | Error | +| N0006 | ✔ DO case two-letter acronyms with the same case. | Error | +| N0007 | ✔ DO case three+ letter acronyms the same as a normal word. | Error | +| N0008 | ✖ DO NOT suffix property names with primitive type information unless the type is temporal. | Error | +| N0009 | ✔ DO suffix `Edm.Date` property names with `…Date`. | Error | +| N0010 | ✔ DO suffix `Edm.Time` property names with `…Time`. | Error | +| N0011 | ✔ DO suffix `Edm.DateTime` property names with `…DateTime`. | Error | +| N0012 | ✖ DO NOT prefix properties with the name of the type unless it is significantly more readable. | Error | +| N0013 | ✔ CONSIDER using a property name from the shared property name list. | Info | +| N0014 | ✖ AVOID using `type` as the name of a property. | Warning | +| N0015 | ✖ AVOID using `Mail` in property names. Instead use `Email`. | Warning | + +## Modeling + +| Id | Name | Severity | +| ----------------------- | ----------------------------------------------------------------------------------------------- | -------- | +| M0001 | ✔ DO make the key of every entity a single property with name `id` and type `Edm.String`. | Error | +| M0002 | ✔ DO make every entity inherit from `microsoft.graph.entity`. | Error | +| M0003 | ✖ DO NOT use parallel collections; use collections of complex types instead. | Error | +| M0004 | ✔ CONSIDER use a proper collection rather than `property1`, `property2`, etc. | Warning | +| M0005 | ✖ AVOID denormalization unless it is necessary for developer scenarios. | Warning | +| M0006 | ✔ CONSIDER using an evolvable enumeration for enumerations that will add members in the future. | Warning | +| M0007 | ✖ AVOID operations such as actions and functions whenever possible. | Warning | +| M0008 | ✔ CONSIDER using inheritance when types share three or more properties in common. | Warning | +| M0009 | ✖ DO NOT use a complex type with an `id`. Instead use an entity. | Error | +| M0010 | ✔ CONSIDER making enums with two options a boolean. | Warning | +| M0011 | ✔ CONSIDER making enums with has flags be a power of two. | Warning | +| M0012 | ✖ DO NOT have entity types override their base properties. | Error | +| M0013 | ✖ DO NOT have complex types override their base properties. | Error | +| M0014 | ✔ DO have valid navigation properties for entity sets. | Error | +| M0015 | ✔ DO have valid navigation properties for singletons. | Error | + +## Request patterns + +| Id | Name | Severity | +| ----------------------- | ---------------------------------------------------------------------------------------------- | -------- | +| H0001 | ✔ DO use `GET …/{collection}` and `GET …/{collection}/{id}` for listing and reading resources. | Error | +| H0002 | ✔ DO use `POST …/{collection}` for creating resources. | Error | +| H0003 | ✔ DO use `PATCH …/{collection}/{id}` for updating resources. | Error | +| H0004 | ✖ AVOID using `PUT …/{collection}/{id}` for updating resources. | Warning | +| H0005 | ✖ DO NOT use `PATCH` to replaces resources or `PUT` to partially update resources. | Error | +| H0006 | ✖ AVOID patterns that require multiple round trips to complete a single logical action. | Warning | +| H0007 | ✔ CONSIDER supporting `return`, `omit-nulls`, and `include-evolvable-enums` preferences. | Warning | +| H0008 | ✔ DO make requests and responses symmetrical. | Error | + +## Serialization + +| Id | Name | Severity | +| ----------------------- | ---------------------------------------------------------------------- | -------- | +| S0001 | ✔ DO use an object as the root of all JSON payloads. | Error | +| S0002 | ✔ DO use a `value` property in the root object to return a collection. | Error | +| S0003 | ✔ DO return a `@odata.context` URL on all responses. | Error | +| S0004 | ✔ DO include `@odata.type` annotations when the type is ambiguous. | Warning | +| S0005 | ✔ DO return JSON by default. | Error | +| S0006 | ✔ DO minify responses. | Info | + +## Authorization + +| Id | Name | Severity | +| ----------------------- | -------------------------------------------------------------------------- | -------- | +| A0001 | ✖ DO NOT use a scope ending with `.Read` to authorize a data modification. | Error | +| A0002 | ✔ DO use `POST …/{collection}` for creating resources. | Error | +| A0003 | ✔ DO use `PATCH …/{collection}/{id}` for updating resources. | Error | +| A0004 | ✖ AVOID using `PUT …/{collection}/{id}` for updating resources. | Warning | + +## Errors + +| Id | Name | Severity | +| ----------------------- | ----------------------------------------------------------------------------------------------- | -------- | +| E0001 | ✔ DO return an `error` property with a child `code` property in all error responses. | Error | +| E0002 | ✔ DO return a `403 Forbidden` error when insufficient scopes are present on the auth token. | Error | +| E0003 | ✔ CONSIDER returning a `404 Not found` error if a `403` would result in information disclosure. | Error | +| E0004 | ✔ DO return a `429 Too many requests` error when the caller has exceeded throttling limits. | Error | diff --git a/graph/gmm/odata-conformance.md b/graph/gmm/odata-conformance.md new file mode 100644 index 00000000..c383420b --- /dev/null +++ b/graph/gmm/odata-conformance.md @@ -0,0 +1,81 @@ +--- +title: OData conformance +--- + +# OData Conformance Requirements for Microsoft Graph + +The conformance requirements are listed below in different levels. These levels correspond to whether a service must implement them or if they are optional. The actual OData conformance requirements and details (sections mentioned beside the point) can be found at this [**link.**](http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part1-protocol/odata-v4.0-errata03-os-part1-protocol-complete.html#_Toc453752324) + +## Level 1 + +Minimum Requirements a workload service needs to meet in order to onboard onto graph successfully. + +- MUST return data according to at least one of the OData defined formats (section 7). We require JSON +- MUST support server-driven paging when returning partial results (section 11.2.5.7) +- MUST successfully parse the request according to [OData-ABNF][2] for any supported system query string options. +- MUST expose only data types defined in [OData-CSDL][2] +- MUST NOT violate any OData update semantics (section 11.4 and all subsections) +- MUST publish metadata at $metadata according to [OData-CSDL][2] (section 11.1.2) +- MUST support the resource path conventions defined in [OData URL][2] + +## Level 2 + +These requirements are highly recommended, but are still optional. This may vary based on the workload service, where some conformance requirements must be implemented. + +- Return the appropriate OData-Version header (section 8.1.5) +- Conform to the semantics the following headers, or fail the request + - Accept (section 8.2.1) + - OData-MaxVersion (section 8.2.7) +- Include edit links (explicitly or implicitly) for all updatable or deletable resources according to [OData-Atom][2] and [OData-JSON][2] +- Support POST of new entities to insertable entity sets (section 11.4.1.5 and 11.4.2.1) +- Support PATCH to all edit URLs for updatable resources (section 11.4.3) +- Support DELETE to all edit URLs for deletable resources (section 11.4.5) +- Support DELETE to $ref to remove an entity from an updatable navigation property (section 11.4.6.2) +- Return a Location header with the edit URL or read URL of a created resource (section 11.4.1.5) +- Support $select (section11.2.4.1) +- Support casting to a derived type according to [OData URL][2] if derived types are present in the model +- Support $top (section 11.2.5.3) +- Support $filter (section 11.2.5.1) +- Support eq, ne filter operations on properties of entities in the requested entity set (section 11.2.5.1.1) +- Support the $skip system query option (section 11.2.5.4) +- Support the $count system query option (section 11.2.5.5) +- Support $orderby asc and desc on individual properties (section 11.2.5.2) + +## Level 3 + +These Odata conformance requirements can be implemented based on the discretion/requirements of the workload. To decide whether a workload service needs to implement one or more these, please contact Microsoft Graph team. + +- Support $expand (section 11.2.4.2) +- Support POST of new related entities to updatable navigation properties (section 11.4.6.1) +- Support POST to $ref to add an existing entity to an updatable related collection (section 11.4.6.1) +- Support PUT to $ref to set an existing single updatable related entity (section 11.4.6.3) +- Support if-match header in update/delete of any resources returned with an ETag (section 11.4.1.1) +- Include the OData-EntityId header in response to any create or upsert operation that returns 204 No Content (Section 8.3.3) +- Support Upserts (section 11.4.4) +- Support PUT and PATCH to an individual primitive (section 11.4.9.1) or complex (section 11.4.9.3) property (respectively) +- Support DELETE to set an individual property to null (section 11.4.9.2) +- Support deep inserts (section 11.4.2.2) +- Support /$value on media entities (section 4.10. in [OData URL][2] and individual properties (section 11.2.3.1) +- Support aliases in $filter expressions (section 11.2.5.1.3) +- Support additional filter operations (section 11.2.5.1.1) and MUST return 501 Not Implemented for any unsupported filter operations (section 9.3.1) +- Support the canonical functions (section 11.2.5.1.2) and MUST return 501 Not Implemented for any unsupported canonical functions (section 9.3.1) +- Support $filter on expanded entities (section 11.2.4.2.1) +- Support the $search system query option (section 11.2.5.6) +- Support $expand (section 11.2.4.2) +- Support the lambda operators any and all on navigation- and collection-valued properties (section 5.1.1.5 in [OData URL][2]) +- Support $expand (section 11.2.4.2) + - Support returning references for expanded properties (section 11.2.4.2) + - Support $filter on expanded entities (section 11.2.4.2.1) + - Support cast segment in expand with derived types (section 11.2.4.2.1) + - Support $orderby asc and desc on individual properties (section 11.2.4.2.1) + - Support the $count system query option for expanded properties (section 11.2.4.2.1) + - Support $top and $skip on expanded properties (section 11.2.4.2.1) + - Support $search on expanded properties (section 11.2.4.2.1) + - Support $levels for recursive expand (section 11.2.4.2.1.1) +- Support batch requests (section11.7 and all subsections) +- Support Asynchronous operations (section 8.2.8.8) +- Support Delta change tracking (section 8.2.8.6) +- Support cross-join queries defined in [OData URL][2] +- Support a conforming OData service interface over metadata (section 11.1.3) + +[2]: http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part1-protocol/odata-v4.0-errata03-os-part1-protocol-complete.html#ABNF diff --git a/graph/guidelines.md b/graph/guidelines.md new file mode 100644 index 00000000..a6525ee8 --- /dev/null +++ b/graph/guidelines.md @@ -0,0 +1,526 @@ +--- +title: Design your API +owner: mastaffo +--- + +# Design your API + + + +> "Make the API to your library as boring as possible. You want the functionality to be interesting, not the API." -- Chris Sells, [Framework Design Guidelines][fdg] + +API design is a crucial but often overlooked aspect of the API development process. + +## Your most important investment + +The design of your API is arguably the most important investment you will make in it. The design of your API is what creates the first impression for developers. + +We want to provide our developers an incredible promise: no matter where they are in Microsoft Graph, the API style should feel familiar. Naming, casing, filtering, pagination, and more are handled the same way through all Graph APIs. + +Consistency is not cheap. This principle is so important that it is listed as the second quality of a well-designed framework in [Framework Design Guidelines](https://www.safaribooksonline.com/library/view/framework-design-guidelines/9780321545671/chapter01.html#ch1): + +> Good framework design does not happen magically. It is hard work that consumes lots of time and resources. If you are not willing to invest real money in the design, you should not expect to create a well-designed framework. + +## Design framework + +Microsoft Graph has a well-defined framework for API design. On the positive side, this well-defined framework reduces [bikeshedding](https://en.wikipedia.org/wiki/Law_of_triviality) and results in a better experience for developers who consume Graph. On the negative side, it's harder for you. You have to learn about how we do API design, and you have less "freedom" in how you design your API. + +Even so, this work results in a clean and simple-to-use experience for our users. We believe 100% that this is the right thing to do for our customers, and we hope you do too. + +| Section | Description | +| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Basic guidance](/add/design/basic-guidance/) | Beginners should start with [basic guidance](/add/design/basic-guidance/), where we provide an overview of the design guidelines that inform our API design. We also provide a recommended approach for people who are new to API design. | +| [Key principles](/add/design/key-principles/) | [Key principles](/add/design/key-principles/) are our non-negotiables. These are the naming, authentication, throttling, and error guidelines that every API must adhere to. | +| [Modeling patterns](/add/design/modeling-patterns/) | We introduce, compare, and contrast different ways to model the schema of your API in [modeling patterns](/add/design/modeling-patterns/). This includes some special things we've introduced, such as [evolvable enumerations](/add/design/modeling-patterns/evolvable-enums.md), so don't miss it! | +| [Common patterns](/add/design/common-patterns/) | The most encouraged API patterns are documented in [common patterns](/add/design/common-patterns/). Common patterns include things we would like to see every API support, such as Webhooks and deltas. | +| [Advanced patterns](/add/design/advanced-patterns/) | Less common patterns are listed in [advanced patterns](/add/design/advanced-patterns/). | +| [Design rules](/add/design/gmm/) | The detailed list of rules enforced by our tooling and API reviewers is available in [GMM](/add/design/gmm/). This is primarily reference content and not something we expect you to read through as you are ramping up. | + +## Section Summary + +Overall, the design section exists to introduce and educate you on the best practices for building and adding your new API to Microsoft Graph. By the end of this section, you should have a working CDSL file, should understand the REST, Odata, and GMM guidelines well enough to work with the GMM testing tool, and be ready to proceed to the review and build stages with a well-designed and though-out architecture. + +## Additional resources + +The links below provide a set of rules to think through as you design your API. + +- [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/) +- [OData Guidelines](http://www.odata.org/documentation/) +- [Microsoft Graph Documentation](https://developer.microsoft.com/en-us/graph/docs/concepts/overview) +- [Microsoft Graph Explorer](https://aka.ms/ge) + +::: tip [Need help?] +If you have additional design modeling questions (Identity team only), please ask them on the [Identity API Review Team "General" channel](https://teams.microsoft.com/l/channel/19%3a1013992de7d84c68bce90f7ae69f306a%40thread.skype/General?groupId=11b6f8e9-39e6-41f0-9fdb-dbeda26d4378&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) +::: + +[fdg]: https://www.safaribooksonline.com/library/view/framework-design-guidelines/9780321545671/chapter01.html + + +## Basic Guidance + +Web APIs are already more than 20 years old. The SOAP specification was created in 1998, and the dissertation that became REST was written in 2000. It would take many hours to truly examine the history of APIs, so let's focus on the basics. + +### Resource-oriented architecture + +The first thing we need to discuss is architecture. Leonard Richardson, the author of RESTful Web Services and the more recent RESTful Web APIs, classifies APIs into three architectural categories: resource oriented, RPC oriented, and hybrid (a combination of the two). + +It is critical for us to understand the difference in architectures before we can meaningfully talk about RESTful APIs. The biggest difference is arguably the number of "locations" an operation can be invoked. In RPC architectures, there are comparatively few places to invoke an operation. For instance, SOAP APIs typically expose a single URL, and callers of that API send both an action (the operation to invoke) as well as a request message that contains all of the necessary parameters for invoking the operation. + +REST architectures, on the other hand, have many more "locations" an operation can be invoked. REST focuses on resources, each of which have a unique location. REST uses a URI to represent this location. CRUD operations are typically attached to the resource itself or a parent of the resource. There is often no longer a need for a request message, or if there is, it typically has fewer parameters because the operation is attached to the resource itself. + +We should note right up front that neither architectural style is "evil". They both have pros and cons, and both architectures can meet most needs. Consider the analogy of object-oriented programming versus functional programming. They are both valid styles, and have pros and cons, but neither is "evil". + +### History of REST + +The dissertation that became REST was actually called "Architectural Styles and the Design of Network-based Software Architectures". This dissertation was written by Roy Fielding in 2000, and was based in large part on his experience designing HTTP and URIs. + +If we reflect for a moment on how the Web works, we will better understand RESTful APIs. The Web is, at its most basic essence, a collection of resources – an enormous collection of resources. Web browsers are able to work with all Web sites regardless of whether they were written in pure HTML, Java, Python, or .NET. This all works because of HTTP and URIs. URIs give us a means to uniquely address a resource – a Web page, an image, a stylesheet, or a form submission resource. + +#### Identifying resources + +The unique identifier provided as a URI allows a Web browser to understand and request a particular resource. It also allows resources to be referenced from other resources. This basic concept is known as hypermedia – the linking of resources together using URIs. The URI is useful to many other tools on the Internet, however. The URI is what allows search engines to identify and index resources. In turn, this allows us to search for resources and find the unique identifier that represents that resource. Similarly, proxies are able to cache resources by their identifier either for security, performance, or other purposes. URIs are critical to understanding a resource. + +As a side note, any URI technically constitutes a resource identifier. The URI contains everything from the scheme (HTTP or HTTPS) to the fully qualified domain name to the path to the query string parameters. Furthermore, resources in the purest sense can be a collection of other resources or an individual resource. That said, for Microsoft Graph we have further constrained the definition of "resource" in two ways: + +1. Resources should be individual things, such as a single person or a single task. It is useful to distinguish a collection of individual things as a "resource collection". +2. Query string parameters should not be part of the resource identifier. Most API designs include as much of the resource identification part of the URI as possible in the main URI itself. + +Again, these are Graph preferences and not part of the URI spec. The URI spec uses the broader technical definition. + +#### Using resources + +URIs give us the ability to identify a resource. HTTP gives us the ability to do something with that resource. HTTP has a bunch of built-in methods including `GET` (typically for retrieving a resource), `POST` (often used for creating a resource), and `PATCH` (often used for updating a resource. HTTP has semantics around virtually every aspect of transport from authorization to status to how to construct the request or response headers and body. The HTTP specification is quite large and reasonably mature, so in most cases the guidance in the spec is very clear. + +### Be _openionated_ + +Openionated = opinionated sometimes, open-minded other times + +Given the overall space (public standards, specifications, technology that is used by billions of devices), it is not hard to find people who treat the space as a religion, and publicly shame others who violate (whether from ignorance or on purpose) the specification. REST APIs also have this issue, even though there is only the dissertation and not a public standard for REST. + +> As Microsoft employees, we need to be respectful of others and understand that there are a variety of reasons to do something differently. So first and foremost, engage in dialogue with others and always try to view things optimistically. + +#### Be opinionated when the spec is clear + +That said, there are places to be more opinionated. For instance, parts of the HTTP specification are quite clear and the implications of violating the specification are severe. Let's consider two examples. + +1. The HTTP specification says that `GET` should be both _safe_ and _idempotent_. _Safe_ in this case means that the request does not cause a change in server state, _idempotent_ means that the operation is repeatable many times with the same result. However, some people violate these guidelines and create new resources by sending a `GET` to `{somecollection}/new`. The problem with this design is the billions of devices that understand HTTP. In most cases, those devices understand that the result of a `GET` is cacheable because it is safe and idempotent. Unfortunately, this API design breaks those assumptions and so devices that believe they are working with proper HTTP may in fact be causing significant problems by accessing the API. +2. Similarly, the status codes associated with an HTTP response are not a good forum for creativity. The status codes in the HTTP specification (apart from `418`) all exist for very specific reasons and have benefit to the ecosystem around them. Specification authors do not make their specifications longer than necessary because it's fun to write in spec language. The error codes in the specification are what allow us to write generic clients that understand how to handle a challenge response, or `retry-after`. It is similarly critical for devices to be able to know that if they don't understand error code `474`, they can safely treat it as `400` and know that the client is sending an improper request. + +#### Be open-minded when the spec is unclear + +In other cases the spec is either vague, has evolved, or specifically identifies alternative ways of achieving something. For instance, when the HTTP spec was first created, there was no `PATCH` method. Architects and developers found the need for this method over time, especially with the advent of APIs. The semantic of `PATCH` is that it allows a partial replacement to update a resource. The alternative, `PUT`, requires the full resource to be sent, and the server replaces the resource entirely. In some cases this is inefficient, in other cases it actually doesn't work (for example, if the resource has server-computed values). In any case, the addition of `PATCH` as a different specification gives API developers a choice of how to support update. Guidance is especially necessary when there are multiple conflicting or confusing means of achieving something. (For what it's worth, we recommend always supporting `PATCH` and additionally supporting `PUT` if it makes sense in your scenario.) + + +## Key Principles + +Consistent naming is foundational for API usability. JSON is the leading serialization format for HTTP APIs, so this guidance is designed with JavaScript in mind. + +Property and type names appear in URLs and payloads. + +::: tip REFERENCE +These guidelines draw on the [naming section](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#17-naming-guidelines) of the [Microsoft REST API Design Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md). See also "Naming" in [GMM Level 1](https://msgo.azurewebsites.net/add/design/gmm/gmm-level-1.html). + +The .NET [Framework Design Guidelines](https://www.safaribooksonline.com/library/view/framework-design-guidelines/9780321545671/chapter03.html#ch3) provide rationale for naming guidelines. + +::: + +### General Guidelines + +::: tip ✔ DO use `lowerCamelCase` for _all_ names. + +- Right: `automaticRepliesStatus`. +- Wrong: `kebab-case` or `snake_case`. + +::: + +::: warning ✖ AVOID redundant words in names. + +- Right: `/places/{id}/`**_type_** and `/phones/{id}/`**_number_** +- Wrong: `/places/{id}/`_**placeType**_ and `/phones/{id}/`**_phoneNumber_** + +::: + +::: warning ✖ AVOID using brand names in type or property names. + +- Right: `chat` +- Wrong: `teamsChat` + +::: + +::: warning ✖ AVOID using acronyms or abbreviations unless they are broadly understood. + +- Right: `url` or `htmlSignature` +- Wrong: `msodsUrl` or `dlp` + +::: + +::: tip ✔ DO use singular nouns for type names. + +- Right: `address` +- Wrong: `addresses` + +::: + +::: tip ✔ DO use plural nouns for collections (for listing a type or collection properties). + +- Right: `addresses` +- Wrong: `address` + +::: + +::: tip ✔ DO pluralize the noun even when followed by an adjective (a "postpositive"). + +- Right: `passersby` or `mothersInLaw` +- Wrong: `notaryPublics` or `motherInLaws` + +::: + +### Casing + +::: tip ✔ DO case two-letter acronyms with the same case. + +- Right: `ioLimit` or `totalIOAmount` +- Wrong: `iOLimit` or `totalIoAmount` + +::: + +::: tip ✔ DO case three+ letter acronyms the same as a normal word. + +- Right: `fidoKey` or `oauthUrl` +- Wrong: `webHTML` + +::: + +::: danger ✖ DO NOT capitalize the word following a prefix or words within a compound word. + +- Right: `subcategory`, `geocoordinate` or `crosswalk` +- Wrong: `metaData`, `semiCircle` or `airPlane` + +::: + +::: tip ✔ DO capitalize within hyphenated and open (spaced) compound words. + +- Right: `fiveYearOld`, `daughterInLaw` or `postOffice` +- Wrong: `paperclip`, `changingroom` or `fullmoon` + +::: + +### Prefixes and Suffixes + +::: tip ✔ DO suffix date and time properties. + +- Right: `dueDate`—an `Edm.Date` +- Right: `createdDateTime`—an `Edm.DateTimeOffset` +- Right: `recurringMeetingTime`—an `Edm.TimeOfDay` +- Wrong: `dueOn` or `startTime`, both an `Edm.DateTimeOffset` + +::: + +::: danger ✖ DO NOT suffix property names with primitive type names unless the type is temporal. + +- Right: `isEnabled` or `amount` +- Wrong: `enabledBool` + +::: + +::: tip ✔ DO prefix property names for properties concerning a different entity. + +- Right: `siteWebUrl` on `driveItem`, or `userId` on `auditActor` +- Wrong: `webUrl` on `contact` when its the `companyWebUrl` + +::: + +### Common property names + +| Approved name | Type | Use | +| ---------------------- | -------------- | --------------------------------------------------------- | +| `displayName` | String | A label that can be displayed or read aloud. Not `name`. | +| `webUrl` | String | The web page for viewing or editing this entity. | +| `url` | String | A URL to a resource. (In Graph often holds the `webUrl`.) | +| `lastModifiedDateTime` | DateTimeOffset | The last time this entity changed. | +| `createdDateTime` | DateTimeOffset | The time this entity was created. | +| `createdBy` | identitySet | The creator of this entity. | +| `createdByUser` | user | The user in `/users` who created this entity. | + + + +## Modelling Patterns + +### Adding new subtypes + +Table of Contents + +- [Overview](#overview) +- [Background](#background) +- [Risks](#risks) +- [Mitigation](#mitigation) +- [Shielding clients](#shielding-clients) + +This article discusses the consequences of introducing a new sub-type in the Microsoft Graph schema for a type that are used in collections. + +A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes (see [modeling variants](modeling-variants.md)). This allows to model collections of objects that have slightly different behavior. The common behavior is represented in the base type and the variations in a subtype, a concept very familiar from OO programming languages. It is straightforward to add a new subtype to the hierarchy with some consequences to the backwards compatibility as shown below. + +#### Overview + +OData allows to design collections of entities (entity sets, multi valued navigation properties) with values of different types. Currently these different types have to be subtypes of a common base type (often an abstract type). In the current version of Microsoft Graph are many collections of items that represent slightly different things, variants of one concept. For example, the [managedAppPolicy](https://docs.microsoft.com/en-us/graph/api/resources/intune-mam-managedapppolicy?view=graph-rest-1.0) type represents a base type for a variety of platform specific policies including for example a windowsInformationProtection policy. And when sending a GET request to the URL `/deviceAppManagement/managedAppPolicies` a collection of a mix of the sub-types is returned. +Most prominent are the collections of type directoryObject, an abstract base entity type that is implemented by types like user, groups, devices, etc.. + +Even though OData has means to express these subtypes and adding new subtypes is syntactically a backwards compatible change, there are situations that impose some risk to break client applications. + +This article discusses the steps to ensure backwards compatibility of adding new subtypes that are potentially returned in collections. + +#### Background + +For heterogeneous collections, OData ensures that the client is able to distinguish the different types of the element of an collections. So for example querying + +```HTTP +GET https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners +``` + +returns a collection where each element has an additional property `@odata.type` + +```JSON +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#directoryObjects", + "value": [ + { + "@odata.type": "#microsoft.graph.user", + "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", + "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" + // ... + } + ] +} +``` + +Using the `@odata.type` property, the client code can decide how to deserialize the code, for example deciding the class used to create an object/instance. + +Clients must anticipate that new subtypes get introduced and write code to guard against these situations. If not handled appropriately, the existing types most likely don't have the properties to store the returned JSON properties and these values have to be dropped. At the same time properties of the existing types can't get a value assigned. But even in untyped client code, without some compensation, there is no code that "looks" at the unexpected properties and misses the expected ones. + +How to guard against and handle these situations is very specific to the client application and requires to understand the intended semantics of the types returned by the service. + +#### Risks + +There are a few potential risks when new sub-types are introduced. They are all variants of a) the fact that syntactically, previously expected properties might be absent and the received properties are ignored and b) the semantic role that the existing and new subtype play are potentially changing. + +- De-serialization code might break because of missing properties in returned collection items. Even though property X was mandatory on all subtypes previously returned, the new subtype might not have this property and the client code needs to deal with that. + +- Client libraries for strongly typed language might ignore some of the values in the @odata.type property without further configuration and need to be configured to be able to pick the right (client) type to deserialize into. + +- In the client code it is reasonable to assume, based on existing running code, the "world" is exclusively described by the current subtypes. E.g. there is an understanding in Microsoft Graph's directory workload that there are two types of actors: users and servicePrincipals. Introducing a new subtype that can be an actor in directory (e.g. a device) would require a lot of clients to change to anticipate the presence of an object of that type and deal with it's own set of properties. In essence: originally each object was one of n types. After the change, an object can be neither of the n (because it is of the n+1's type). + +#### Mitigation + +Following are some of the techniques to mitigate these situation. + +##### Avoid overgeneralized base types + +If the abstract base type has many subtypes, it is quite likely that specific collection only ever contains a few subtypes. Since the type hierarchy is wide, there is probably some functionality or behavior that is only shared amongst a few but not all subtypes. That is ultimately explaining why some collection only contains some of the subtypes, the ones that share some behavior. + +A well-known example for this situation is the `directoryObject` type which has many sub-types and only one property, `id`. Collections like for example the `owners` property on a `group` is declared as `directoryObject` and in reality only `user`s and `servicePrincipal`s are added to this collection since these are informally the only actors modeled in directory. + +If one only focuses on the hierarchy, one could easily think it is straightforward to add a new subtype. What is necessary is the ensure that adding a new subtype doesn't change the semantic of the type hierarchy and the semantic of the property with it's implicit constraints. + +##### Roll-out sequence + +Microsoft Graph does not return object from a workload that has a type that is not configured in current metadata. That leads to behaviors that is slightly different depending if the object is returned as part of a collection or is requested individually. + +If an object of an un-configured type is returned by the workload as part of a collection, the object just gets excluded from the collection and not returned. Microsoft Graph just doesn't know yet how to serialize the object. + +If an object of an un-configured type is requested directly via an entity set, for example in case of `directoryObject` and a request like `/v1.0/directoryObject/{guid}`, Microsoft Graph returns an empty method body (not a 404 Not Found). It is assumed that the object and it's Id can not be found anywhere in the system and it is safe to ignore it. And Microsoft Graph just doesn't know yet how to serialize the object. + +To make sure that the users do not get exposed to the second behavior, ensure that newly introduced entities are first known by graph before they get added to the collections. +The configuration allows Microsoft Graph to respond with the details/properties of the entities. Without that configuration Microsoft Graph returns no response body. +This leads necessarily to a two-step process of first introducing the entity type but not return them in any of the heterogeneous collections. And only after that returning them as items of collections. This can of be done in relatively rapid succession. + +This is often not a problem since for utterly new entity types, no collection every has items of that type. But if the workload has APIs beside Microsoft Graph, these entities might have been added to the collections through that API. + +##### Allow time for testing + +Inform the clients about the change and allow them to test the changes in beta. Time is required implement the code necessary to deal with the new entity type, both in terms of de-serialization as well as integrating it into the rest of the application. + +##### Communicate the change in semantics + +Even more importantly, it is necessary for the client developers to incorporate the new semantic into their application/service, even if the change is perceived small. The addition of new data needs design changes in the client application. These changes potentially ripple through many layers of that application/service. This requires early communication and clear documentation what the new type represents and why/how it is considered a subtype of the original abstract type of the collection. Without that information the client application will not be able to process that data returned in the responses. + +For example lets assume a situation where owners of a group are people and the only type ever returned as a member of the `owners` property of the `group` entity type is of type `user`. It is reasonable to assume that certain behaviors/functionality exists for these members of the owners collection. For example, every owner has an email address, every owner has a manager. By introducing new types of items to this collection, these assumption might not be true anymore (e.g. owners can be machine accounts without a manager or email). Even though the protocol and client libraries have ways to deal with the transport and de-serialization of these new types, it requires some new design how downstream modules of the client applications/services deal with entities that don't have email addresses or managers. + +#### Shielding Clients + +[TODO: describe upcoming features in Microsoft Graph to ensure full backwards compatibility] + + +### Entity Types and Complex Types + +The [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html), beside many other things, provides a way to describe the structure of the requests and responses of an OData service via the [Common Schema Definition Language](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html) (CSDL). + +CSDL defines a few ways to define [types](https://en.wikipedia.org/wiki/Data_type) and the most prominent are Entity Types and Complex Types. These two have some similarities and some differences that we are going to explore in this article. + +#### Entity Types + +Entity Types are that most common way to define the structure of the requests and responses of an OData service. The standard [defines](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_EntityType): + +> Entity Types are nominal structured types with a key that consists of one or more references to structural properties. An entity type is the template for an entity: any uniquely identifiable record such as a customer or order.. + +There is three things to note in that (arguably terse) definition: + +- By "structured" the standard means that an Entity Type is defined by enumerating its (typed) **properties**. +- "nominal" just refers to the fact that the type **has a name** (a name in the CSDL schema). +- And the most important piece in the context of this article is that an Entity Type declares a **key property** and the consequence is that objects of this type can be uniquely identified through this key. In Microsoft Graph that key is currently always the property named "id". The standards allows more variation and Microsoft Graph might also relax this constraint in the future. + +How they key is used to identify an object is a bit out of the scope of this document. For now it should suffice to say, that it is used as part of the URL to "name" an individual object. For Example in the URL https://localhost/api/authors/50 (or https://localhost/api/authors(50) ), the 50 is the key of an object. + +#### Complex Types + +Complex Types are non-scalar properties of entity types that enable scalar properties to be organized within entities. Complex types consist of a list of properties with no key, and can therefore only exist as properties of a containing entity. You can use complex types to group fields together without exposing them as an independent OData entity. Complex types can contain complex types, that is, they can be deeply nested. + +- A complex type doesn't have keys and therefore cannot exist independently. +- Complex type can only exist as properties of entity types or other complex types. +- It cannot participate in relationships (see navigation properties) directly. + +#### Comparison + +In the example below, we have added an Author as an Entity Type and Address a Complex Type. + +```XML + + + + + + + + + + + + + + +``` + +You can see that Address type does not have any sort of key property. Complex types cannot be tracked on their own, so as a property in the Author class, it will be **tracked as part of** an author object. The consequence is that its lifecycle is coupled to the enclosing Entity Type: When the author gets deleted, the address gets deleted as well. + +#### Summary + +In Summary: + +- Both Entity Types and Complex types are named types that declare a list of properties for the objects of that type. +- An Entity Type always has a key declared whereas a Complex type doesn't. +- Objects of an Entity Type can be directly addressed via an URL but ComplexTypes are always contained in an EntityType object and can only be addressed through a combination of an Entity address and a property name. (see more at [Navigation Properties and Containment](navigation-containment.md)) + +### Adding Members to Enumerations + +Microsoft Graph services sometimes want to add a member to an enumeration type. However, there are barriers. First and foremost, some deserializers (including Json.NET) fail if an enumeration property has a value not found in that property's enumeration type. Second, a client may not deal with enumeration values unknown to it. The `Evolvable Enumerations` pattern and implementation allows a member to be safely be added to an enumeration. + +#### Evolvable Enumerations + +An evolvable enumeration contains the sentinel member `unknownFutureValue` after which new enumeration members are added. Consider the following enumeration: + +```xml + + + + ... + + + +``` + +From this the C# SDK generates: + +```csharp +public enum weekday +{ + monday, + tuesday, + ... + sunday, + unknownFutureValue +} +``` + +The new enumeration member `newday` is added after `unknownFutureValue`: + +```xml + + + + ... + + + + +``` + +From this the C# SDK generates: + +```csharp +public enum weekday +{ + monday, + tuesday, + ... + sunday, + unknownFutureValue, + newday // new value +} +``` + +#### Methods and client opt-in + +On POST, if any enumeration property of the entity contains `unknownFutureValue`, the request will fail with `400 Bad Request`. On PATCH, any enumeration property with value `unknownFutureValue` is ignored--that property is not updated. + +Callers signal their ability to process added members by including the `include-unknown-enum-members` preference: + +```http +GET /me/calendar +Prefer: include-unknown-enum-members +``` + +Upon GET, when this header is absent, `unknownFutureValue` is returned to the caller for enumeration property values that are one of the added enumeration members. When this header is present, the enumeration value is returned unchanged. + +Upon a filtered GET, when this header is absent, if `unknownFutureValue` appears in a `$filter` clause it matches any added enumeration member. For example, if the enumeration members `newday` and `anotherNewDay` have been added to `weekday`, these are equivalent: + +```http +$filter=weekday eq unknownFutureValue +$filter=weekday eq newday or weekday eq anotherNewDay +$filter=weekday ge unknownFutureValue +``` + +If the header is absent, `$filter=weekday` **eq** `unknownFutureValue` matches any new enumeration value. If the header is present, that same filter matches nothing, while `$filter=weekday` **ge** `unknownFutureValue` matches any new enumeration value. (Note: the latter matches new enumeration values whether the header is present or not.) + +#### SDK code generation + +The SDK generates enumeration definitions from the current schema. Requests always include the `include-unknown-enum-members` header. + +At runtime, since other members could have been added to the enumeration after code was generated, values unknown to the generated code are translated to `unknownFutureValue`. When the service sees an enumeration property with the value `unknownFutureValue`, it will ignore it and not update the property. + +#### Resetting an Evolvable Enumeration + +Upon a major version change, `unknownFutureValue` can be moved to the end of the enumeration, making known the previously unknown enumeration members. + +```csharp +public enum weekday +{ + monday, + ... + sunday, + newday, + anotherNewDay, + unknownFutureValue +} +``` + + +## Common Patterns + +## Advanced Patterns + +## Design Rules \ No newline at end of file diff --git a/graph/modeling-patterns/README.md b/graph/modeling-patterns/README.md new file mode 100644 index 00000000..7829cf3a --- /dev/null +++ b/graph/modeling-patterns/README.md @@ -0,0 +1,6 @@ +--- +title: Modeling Patterns +owner: mastaffo +--- + +# Coming soon diff --git a/graph/modeling-patterns/adding-subtypes.md b/graph/modeling-patterns/adding-subtypes.md new file mode 100644 index 00000000..c33fb266 --- /dev/null +++ b/graph/modeling-patterns/adding-subtypes.md @@ -0,0 +1,107 @@ +--- +title: Adding subtypes +owner: chrispre +--- + +# Adding new subtypes + +Table of Contents + +- [Overview](#overview) +- [Background](#background) +- [Risks](#risks) +- [Mitigation](#mitigation) +- [Shielding clients](#shielding-clients) + +This article discusses the consequences of introducing a new sub-type in the Microsoft Graph schema for a type that are used in collections. + +A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes (see [modeling variants](modeling-variants.md)). This allows to model collections of objects that have slightly different behavior. The common behavior is represented in the base type and the variations in a subtype, a concept very familiar from OO programming languages. It is straightforward to add a new subtype to the hierarchy with some consequences to the backwards compatibility as shown below. + +## Overview + +OData allows to design collections of entities (entity sets, multi valued navigation properties) with values of different types. Currently these different types have to be subtypes of a common base type (often an abstract type). In the current version of Microsoft Graph are many collections of items that represent slightly different things, variants of one concept. For example, the [managedAppPolicy](https://docs.microsoft.com/en-us/graph/api/resources/intune-mam-managedapppolicy?view=graph-rest-1.0) type represents a base type for a variety of platform specific policies including for example a windowsInformationProtection policy. And when sending a GET request to the URL `/deviceAppManagement/managedAppPolicies` a collection of a mix of the sub-types is returned. +Most prominent are the collections of type directoryObject, an abstract base entity type that is implemented by types like user, groups, devices, etc.. + +Even though OData has means to express these subtypes and adding new subtypes is syntactically a backwards compatible change, there are situations that impose some risk to break client applications. + +This article discusses the steps to ensure backwards compatibility of adding new subtypes that are potentially returned in collections. + +## Background + +For heterogeneous collections, OData ensures that the client is able to distinguish the different types of the element of an collections. So for example querying + +```HTTP +GET https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners +``` + +returns a collection where each element has an additional property `@odata.type` + +```JSON +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#directoryObjects", + "value": [ + { + "@odata.type": "#microsoft.graph.user", + "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", + "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" + // ... + } + ] +} +``` + +Using the `@odata.type` property, the client code can decide how to deserialize the code, for example deciding the class used to create an object/instance. + +Clients must anticipate that new subtypes get introduced and write code to guard against these situations. If not handled appropriately, the existing types most likely don't have the properties to store the returned JSON properties and these values have to be dropped. At the same time properties of the existing types can't get a value assigned. But even in untyped client code, without some compensation, there is no code that "looks" at the unexpected properties and misses the expected ones. + +How to guard against and handle these situations is very specific to the client application and requires to understand the intended semantics of the types returned by the service. + +## Risks + +There are a few potential risks when new sub-types are introduced. They are all variants of a) the fact that syntactically, previously expected properties might be absent and the received properties are ignored and b) the semantic role that the existing and new subtype play are potentially changing. + +- De-serialization code might break because of missing properties in returned collection items. Even though property X was mandatory on all subtypes previously returned, the new subtype might not have this property and the client code needs to deal with that. + +- Client libraries for strongly typed language might ignore some of the values in the @odata.type property without further configuration and need to be configured to be able to pick the right (client) type to deserialize into. + +- In the client code it is reasonable to assume, based on existing running code, the "world" is exclusively described by the current subtypes. E.g. there is an understanding in Microsoft Graph's directory workload that there are two types of actors: users and servicePrincipals. Introducing a new subtype that can be an actor in directory (e.g. a device) would require a lot of clients to change to anticipate the presence of an object of that type and deal with it's own set of properties. In essence: originally each object was one of n types. After the change, an object can be neither of the n (because it is of the n+1's type). + +## Mitigation + +Following are some of the techniques to mitigate these situation. + +### Avoid overgeneralized base types + +If the abstract base type has many subtypes, it is quite likely that specific collection only ever contains a few subtypes. Since the type hierarchy is wide, there is probably some functionality or behavior that is only shared amongst a few but not all subtypes. That is ultimately explaining why some collection only contains some of the subtypes, the ones that share some behavior. + +A well-known example for this situation is the `directoryObject` type which has many sub-types and only one property, `id`. Collections like for example the `owners` property on a `group` is declared as `directoryObject` and in reality only `user`s and `servicePrincipal`s are added to this collection since these are informally the only actors modeled in directory. + +If one only focuses on the hierarchy, one could easily think it is straightforward to add a new subtype. What is necessary is the ensure that adding a new subtype doesn't change the semantic of the type hierarchy and the semantic of the property with it's implicit constraints. + +### Roll-out sequence + +Microsoft Graph does not return object from a workload that has a type that is not configured in current metadata. That leads to behaviors that is slightly different depending if the object is returned as part of a collection or is requested individually. + +If an object of an un-configured type is returned by the workload as part of a collection, the object just gets excluded from the collection and not returned. Microsoft Graph just doesn't know yet how to serialize the object. + +If an object of an un-configured type is requested directly via an entity set, for example in case of `directoryObject` and a request like `/v1.0/directoryObject/{guid}`, Microsoft Graph returns an empty method body (not a 404 Not Found). It is assumed that the object and it's Id can not be found anywhere in the system and it is safe to ignore it. And Microsoft Graph just doesn't know yet how to serialize the object. + +To make sure that the users do not get exposed to the second behavior, ensure that newly introduced entities are first known by graph before they get added to the collections. +The configuration allows Microsoft Graph to respond with the details/properties of the entities. Without that configuration Microsoft Graph returns no response body. +This leads necessarily to a two-step process of first introducing the entity type but not return them in any of the heterogeneous collections. And only after that returning them as items of collections. This can of be done in relatively rapid succession. + +This is often not a problem since for utterly new entity types, no collection every has items of that type. But if the workload has APIs beside Microsoft Graph, these entities might have been added to the collections through that API. + +### Allow time for testing + +Inform the clients about the change and allow them to test the changes in beta. Time is required implement the code necessary to deal with the new entity type, both in terms of de-serialization as well as integrating it into the rest of the application. + +### Communicate the change in semantics + +Even more importantly, it is necessary for the client developers to incorporate the new semantic into their application/service, even if the change is perceived small. The addition of new data needs design changes in the client application. These changes potentially ripple through many layers of that application/service. This requires early communication and clear documentation what the new type represents and why/how it is considered a subtype of the original abstract type of the collection. Without that information the client application will not be able to process that data returned in the responses. + +For example lets assume a situation where owners of a group are people and the only type ever returned as a member of the `owners` property of the `group` entity type is of type `user`. It is reasonable to assume that certain behaviors/functionality exists for these members of the owners collection. For example, every owner has an email address, every owner has a manager. By introducing new types of items to this collection, these assumption might not be true anymore (e.g. owners can be machine accounts without a manager or email). Even though the protocol and client libraries have ways to deal with the transport and de-serialization of these new types, it requires some new design how downstream modules of the client applications/services deal with entities that don't have email addresses or managers. + +## Shielding Clients + +[TODO: describe upcoming features in Microsoft Graph to ensure full backwards compatibility] diff --git a/graph/modeling-patterns/containment.md b/graph/modeling-patterns/containment.md new file mode 100644 index 00000000..36b36ba1 --- /dev/null +++ b/graph/modeling-patterns/containment.md @@ -0,0 +1,5 @@ +--- +title: Containment +--- + +# Coming soon diff --git a/graph/modeling-patterns/entity-complex.md b/graph/modeling-patterns/entity-complex.md new file mode 100644 index 00000000..f723ce31 --- /dev/null +++ b/graph/modeling-patterns/entity-complex.md @@ -0,0 +1,62 @@ +--- +title: Entity Types and Complex Types +--- + +# Entity Types and Complex Types + +The [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html), beside many other things, provides a way to describe the structure of the requests and responses of an OData service via the [Common Schema Definition Language](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html) (CSDL). + +CSDL defines a few ways to define [types](https://en.wikipedia.org/wiki/Data_type) and the most prominent are Entity Types and Complex Types. These two have some similarities and some differences that we are going to explore in this article. + +## Entity Types + +Entity Types are that most common way to define the structure of the requests and responses of an OData service. The standard [defines](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_EntityType): + +> Entity Types are nominal structured types with a key that consists of one or more references to structural properties. An entity type is the template for an entity: any uniquely identifiable record such as a customer or order.. + +There is three things to note in that (arguably terse) definition: + +- By "structured" the standard means that an Entity Type is defined by enumerating its (typed) **properties**. +- "nominal" just refers to the fact that the type **has a name** (a name in the CSDL schema). +- And the most important piece in the context of this article is that an Entity Type declares a **key property** and the consequence is that objects of this type can be uniquely identified through this key. In Microsoft Graph that key is currently always the property named "id". The standards allows more variation and Microsoft Graph might also relax this constraint in the future. + +How they key is used to identify an object is a bit out of the scope of this document. For now it should suffice to say, that it is used as part of the URL to "name" an individual object. For Example in the URL https://localhost/api/authors/50 (or https://localhost/api/authors(50) ), the 50 is the key of an object. + +## Complex Types + +Complex Types are non-scalar properties of entity types that enable scalar properties to be organized within entities. Complex types consist of a list of properties with no key, and can therefore only exist as properties of a containing entity. You can use complex types to group fields together without exposing them as an independent OData entity. Complex types can contain complex types, that is, they can be deeply nested. + +- A complex type doesn't have keys and therefore cannot exist independently. +- Complex type can only exist as properties of entity types or other complex types. +- It cannot participate in relationships (see navigation properties) directly. + +## Comparison + +In the example below, we have added an Author as an Entity Type and Address a Complex Type. + +```XML + + + + + + + + + + + + + + +``` + +You can see that Address type does not have any sort of key property. Complex types cannot be tracked on their own, so as a property in the Author class, it will be **tracked as part of** an author object. The consequence is that its lifecycle is coupled to the enclosing Entity Type: When the author gets deleted, the address gets deleted as well. + +## Summary + +In Summary: + +- Both Entity Types and Complex types are named types that declare a list of properties for the objects of that type. +- An Entity Type always has a key declared whereas a Complex type doesn't. +- Objects of an Entity Type can be directly addressed via an URL but ComplexTypes are always contained in an EntityType object and can only be addressed through a combination of an Entity address and a property name. (see more at [Navigation Properties and Containment](navigation-containment.md)) diff --git a/graph/modeling-patterns/evolvable-enums.md b/graph/modeling-patterns/evolvable-enums.md new file mode 100644 index 00000000..4547ffd4 --- /dev/null +++ b/graph/modeling-patterns/evolvable-enums.md @@ -0,0 +1,107 @@ +--- +title: Evolvable enums +owners: sanonsen, mastaffo +--- + +# Adding Members to Enumerations + +Microsoft Graph services sometimes want to add a member to an enumeration type. However, there are barriers. First and foremost, some deserializers (including Json.NET) fail if an enumeration property has a value not found in that property's enumeration type. Second, a client may not deal with enumeration values unknown to it. The `Evolvable Enumerations` pattern and implementation allows a member to be safely be added to an enumeration. + +## Evolvable Enumerations + +An evolvable enumeration contains the sentinel member `unknownFutureValue` after which new enumeration members are added. Consider the following enumeration: + +```xml + + + + ... + + + +``` + +From this the C# SDK generates: + +```csharp +public enum weekday +{ + monday, + tuesday, + ... + sunday, + unknownFutureValue +} +``` + +The new enumeration member `newday` is added after `unknownFutureValue`: + +```xml + + + + ... + + + + +``` + +From this the C# SDK generates: + +```csharp +public enum weekday +{ + monday, + tuesday, + ... + sunday, + unknownFutureValue, + newday // new value +} +``` + +## Methods and client opt-in + +On POST, if any enumeration property of the entity contains `unknownFutureValue`, the request will fail with `400 Bad Request`. On PATCH, any enumeration property with value `unknownFutureValue` is ignored--that property is not updated. + +Callers signal their ability to process added members by including the `include-unknown-enum-members` preference: + +```http +GET /me/calendar +Prefer: include-unknown-enum-members +``` + +Upon GET, when this header is absent, `unknownFutureValue` is returned to the caller for enumeration property values that are one of the added enumeration members. When this header is present, the enumeration value is returned unchanged. + +Upon a filtered GET, when this header is absent, if `unknownFutureValue` appears in a `$filter` clause it matches any added enumeration member. For example, if the enumeration members `newday` and `anotherNewDay` have been added to `weekday`, these are equivalent: + +```http +$filter=weekday eq unknownFutureValue +$filter=weekday eq newday or weekday eq anotherNewDay +$filter=weekday ge unknownFutureValue +``` + +If the header is absent, `$filter=weekday` **eq** `unknownFutureValue` matches any new enumeration value. If the header is present, that same filter matches nothing, while `$filter=weekday` **ge** `unknownFutureValue` matches any new enumeration value. (Note: the latter matches new enumeration values whether the header is present or not.) + +## SDK code generation + +The SDK generates enumeration definitions from the current schema. Requests always include the `include-unknown-enum-members` header. + +At runtime, since other members could have been added to the enumeration after code was generated, values unknown to the generated code are translated to `unknownFutureValue`. When the service sees an enumeration property with the value `unknownFutureValue`, it will ignore it and not update the property. + +## Resetting an Evolvable Enumeration + +Upon a major version change, `unknownFutureValue` can be moved to the end of the enumeration, making known the previously unknown enumeration members. + +```csharp +public enum weekday +{ + monday, + ... + sunday, + newday, + anotherNewDay, + unknownFutureValue +} +``` diff --git a/graph/modeling-patterns/modeling-variants.md b/graph/modeling-patterns/modeling-variants.md new file mode 100644 index 00000000..0f2d97fb --- /dev/null +++ b/graph/modeling-patterns/modeling-variants.md @@ -0,0 +1,75 @@ +--- +title: Modeling variants +owner: chrispre +--- + +# Modeling Variants + +Frequently we encounter situations where a certain piece of data in Microsoft Graph comes in different variants. Depending on the situation we call these variants, kinds, types, etc.. Some examples are + +- owners of groups can be either a `user` or a `servicePrincipal`. +- an approver of a request can be a single user, a group, etc.. +- the end of a recurring event can be after a number of repetitions or at a certain date, or never. + +All these variants have different properties representing the information needed in these cases. + +OData and Microsoft Graph offer different ways to model the API and these different variants. We'll describe those here, and list the advantages and disadvantages of each modelling technique. + +In the remainder of the document we are using the term "variant" instead of "kind", "flavor", "type". "type" is defined by OData and we do not want to presume there has to be a type per variant. + +## Approaches + +There are different approaches to design a model in situations with multiple variants of common concept. We are going to compare three common patterns that we see in Microsoft Graph today. + +### Type Hierarchy + +A shallow **type hierarchy**: One abstract base type with a few common properties and one sub-type for each variant. OData adds `@odata.type` properties to the JSON representation when instances of these types are returned so that a client can quickly distinguish them. + +One prominent example is the base type [graph.outlookItem](https://docs.microsoft.com/en-us/graph/api/resources/outlookitem?view=graph-rest-1.0) with subtypes like [message](https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0), [contact](https://docs.microsoft.com/en-us/graph/api/resources/contact?view=graph-rest-1.0), [event](https://docs.microsoft.com/en-us/graph/api/resources/event?view=graph-rest-1.0). + +### Facets + +A single entity type with **facets**: One type in the schema with common properties and one property (of complex type) per variant. The facet properties only have a value when the object represents that variant. + +This can be seen for example in [driveItem](https://docs.microsoft.com/en-us/graph/api/resources/driveitem?view=graph-rest-1.0) where there are four variants (folder, file, image, photo) and one property per variant with the same name. These properties are modeled as a complex types that holds all information for that specific facet/variant. (e.g. just the `element count` property for folder and eight different properties for photo like camera model and settings). + +### Flat + +A **flat** bag of properties: One entity type with all the potential properties plus an additional property to distinguish the variants, often called `type`. The `type` property describes the variant and also defines properties are required/meaningful for the variant given by the `type` property. + +Since the name `type` could be confused with the notion of type in OData, it is often recommended to qualify the property name. E.g. `recurrenceType` instead of `type`. + +A good example for this is the recurrencePattern and recurrenceRange types (both properties on [patternedRecurrence](https://docs.microsoft.com/en-us/graph/api/resources/patternedrecurrence?view=graph-rest-1.0)). +The recurrencePattern has 6 variants expressed as 6 different values of the `type` property (e.g. daily, weekly, ...). +The key here is that for each of these values, some properties are meaningful and others are ignored. (e.g. `daysOfWeek` is relevant when `type` is `weekly` but not when it is `daily`). + +## Pros and Cons + +Below are a few pros and cons to decide which pattern to use. + +- In **[hierarchy](#type-hierarchy)**, the interdependencies of properties, i.e. which properties are relevant for which variants, is fully captured in metadata and client code can potentially leverage that to construct and/or validate requests. +- Introducing new cases in **[hierarchy](#type-hierarchy)** is relatively isolated (which is why it is so familiar to OOP) and is considered backwards compatible (at least syntactically). But see the note about [changing sematics](#semantics) below. +- Introducing new cases/variants in **[facets](#facets)** is straightforward. One needs to be careful since it can introduce situations where previously exactly one of the facets was non-null and now all the old ones are null. For example imagine a new facet "shortcut" is added to the example above where everything was one of folder,file,image,photo. Adding the shortcut facet means that there are now object with all of the previous four are null. + This is not unlike adding new subtypes in the hierarchy pattern or adding a new type value in the flat pattern. +- **[hierarchy](#type-hierarchy)** and **[facets](#facets)** (to a slightly lesser degree) are well suited for strongly typed client programming languages. Whereas **[flat](#flat)** is more familiar to developers of less strongly typed languages. +- **[facets](#facets)** has the potential to model what is typically associated with multiple inheritance (but it is not inheritance so please don’t quote me). Just to illustrate the point and constructing a highly hypothetical scenario, in the OneDrive example, having an item be a folder and a photo is easy to represent. +- **[facets](#facets)** and **[flat](#flat)** lend to syntactically simpler filter query expression. **[hierarchy](#type-hierarchy)** is more explicit but requires the less well known cast segments in the filter query. For example, if one wants to filter on the importance of a mail in a collection of outlookItems, one first needs to "cast" to mailItem to then filter on the importance property: `$filter=microsoft.graph.mailItem/importance eq 'High'`. +- **[flat](#flat)** might resemble a structure that that developers are familiar with from on-prem products and their API (e.g. recurrence in Microsoft Graph is modeled after Exchange Server's model). Even though the Graph API can and should abstract from the implementation details this can have benefits in documentation and adoption. +- **[hierarchy](#type-hierarchy)** can become hard to maintain if the base type is quite abstract and the hierarchy is relatively wide. Lets assume a situation where collections are modeled using the base type with many sub-types, but the actual elements of the collection are only ever one or two of the sub-types. When a new subtype gets introduced and the collection(s) quickly contain elements of this new sub-type, client code has to react to these changes. It is important to check if this changes the semantics of the property (actual or assumed). See also [changing semantics](#semantics) below. +- Even though not frequently used in Microsoft Graph, **[hierarchy](#type-hierarchy)** can be refined by annotating the collections with OData `derived type constraints` (see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Validation.V1.md)). This annotation restricts the values to certain sub-trees of an inheritance hierarchy. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return object of a type that is semantically not suitable. + +## Future + +The OData team is looking for feedback what is missing in terms of modeling tools and expressiveness that can help making these design decisions. + +One of the options to explore that helps address some of the cons with overly broad subtype hierarchy is the OData annotation term `MayImplement` (see [Core vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md)), a feature that is not yet implemented in the OData libraries or Microsoft Graph. + +The `MayImplement` annotation is defined as + +> A collection of qualified type names outside of the type hierarchy that instances of this type might be addressable as by using a type-cast segment + +This would allow to keep the type hierarchy narrow but still have some objects cast to a type outside that hierarchy. Please contact us to discuss if this could be helpful for your scenario. + +## Summary + +As can be seen in a few of the Pros and Cons, one of the important aspects discussed here, is that the API design goes beyond the syntactical aspects of the API and it is important to plan ahead how the API evolves, lay the foundation, and allow the users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change**. The different modeling patterns as described above, differ in how they express syntax and semantic and how they allow the API to evolve without breaking compatibility. diff --git a/graph/modeling-patterns/navigation-containment.md b/graph/modeling-patterns/navigation-containment.md new file mode 100644 index 00000000..32e6a395 --- /dev/null +++ b/graph/modeling-patterns/navigation-containment.md @@ -0,0 +1,7 @@ +--- +title: Navigation Properties and Containment +--- + +# Navigation Properties and Containment + +Coming soon diff --git a/graph/modeling-patterns/odata-types-primer.md b/graph/modeling-patterns/odata-types-primer.md new file mode 100644 index 00000000..c7cb7e41 --- /dev/null +++ b/graph/modeling-patterns/odata-types-primer.md @@ -0,0 +1,6 @@ +--- +title: "OData type system primer" +owner: mastaffo +--- + +# Coming soon diff --git a/graph/modeling-patterns/shared-types.md b/graph/modeling-patterns/shared-types.md new file mode 100644 index 00000000..0eadf0f5 --- /dev/null +++ b/graph/modeling-patterns/shared-types.md @@ -0,0 +1,18 @@ +--- +title: Shared types +owner: mastaffo +--- + +# Working with shared types aka referencing existing models from your schema + +You will frequently need to connect your models to other entities on the Graph (indeed, this is a key value of the Microsoft Graph). +To do this, you will have to provide reference versions of the entities you need in your own schema. + +For each entity you need to refer to: + +- Add a declaration to your own schema. This will live under your own namespace, so refer to it as though it was in your own namespace +- Omit the `ags:IsMaster` annotation to indicate that your service does not master this entity +- Do not express inheritance relationships between external entities +- Do not include a copy of the 'virtual' entity named `entity` - this is synthesized by Microsoft Graph itself + +Duplicate enumerations and complex types - these must be expressed (and identical) in every workload that shares them. diff --git a/graph/modeling-patterns/type-namespaces.md b/graph/modeling-patterns/type-namespaces.md new file mode 100644 index 00000000..5d370df6 --- /dev/null +++ b/graph/modeling-patterns/type-namespaces.md @@ -0,0 +1,191 @@ +--- +title: "Type namespaces" +owner: sanonsen, mastaffo +--- + +# Type namespaces + +Types should be declared in an appropriate namespace. As with traditional compiled libraries, putting types in namespaces creates a better developer experience. + +::: warning +Type namespaces are not 1:1 with URL segmentation. For guidance on URL segmentation, see [[Singletons]]. +::: + +## Namespace usage + +Namespaces are used both at runtime and in the developer experience. + +### Runtime + +At runtime, namespaces appear when using type cast segments and in `@odata.type` annotations. + +Type cast segments allow the caller to filter a collection to a given subtype. This usage is rare, but does happen in Microsoft Graph, e.g.: + +```http +GET https://graph.microsoft.com/v1.0/directory/deletedItems/microsoft.graph.user +``` + +We are working to make type cast segment unqualified when the type name is unambiguous. The effect of this change will be that the namespace in the type cast segment is only required when multiple types with that name exist. + +```http +GET https://graph.microsoft.com/v1.0/directory/deletedItems/user +``` + +Type cast segments also appear in `@odata.type` annotations. `@odata.type` annotations are only included if: + +- The type is ambiguous, such as when the type appears in a collection of a supertype. +- Type information was explicitly requested by asking for `odata.metadata=full`. + +### Developer experience + +Namespaces also affect the developer experience: + +| Area | Effect | +| -------------- | ------------------------------------------------- | +| SDKs | Types are generated in the appropriate namespace. | +| Docs | API reference is organized by namespace. | +| Changelog | Changes are categorized by namespace. | +| Graph Explorer | No effect. | + +## Namespace declaration + +The type namespace is declared in the `Namespace` attribute on the `Schema` that is uploaded to Microsoft Graph. + +```xml + +... + +``` + +### Historic behavior + +Until late 2018, Microsoft Graph merged all types into a single namespace, `microsoft.graph`, effectively ignoring the namespace specified in the schema. When the same type name appeared in multiple schemas, the types were merged. This provides teams with the ability to extend a type owned by a different team. + +There are several facets of the historic behavior that should be maintained: + +- Workloads should be able to explicitly declare types in the namespace `microsoft.graph`. +- Workloads should be able to extend types owned by other workloads. +- Types with the same name and namespace should be merged to allow extensibility. + +### Backwards compatibility + +It is a breaking change to change a type's namespace. Types that are in the namespace `microsoft.graph` in `v1.0` must stay in that namespace until Graph 2.0. A workload may technically start introducing types in their custom namespace even if the bulk of their types are in `microsoft.graph`. It is up to API reviewers to determine on a case-by-case basis whether this makes sense. + +## Namespace heuristic + +Microsoft Graph uses a specific heuristic to determine whether the namespace specified on the schema should be exposed publicly. This has two advantages. + +1. No changes need to be made to existing schemas or workloads to ensure that they keep working without introducing a breaking change. +2. The heuristic enforces that all namespaces must begin with `microsoft.graph.`. + +The heuristic is simple: if the namespace string starts with `microsoft.graph.` (case-insensitive, but must include the trailing `.`), the namespace will be publicly exposed. If the namespace does not begin with that exact string, all types in the schema will be coerced into the `microsoft.graph` namespace. + +| Schema namespace (case-insensitive match) | Public namespace | +| -------------------------------------------- | -------------------------------------------- | +| `MyNamespace` | `microsoft.graph` | +| `Microsoft.Graph` | `microsoft.graph` | +| `Microsoft.Graph.MyNamespace` | `microsoft.graph.myNamespace` | +| `Microsoft.Graph.MyNamespace.MySubNamespace` | `microsoft.graph.myNamespace.mySubNamespace` | + +### Namespace coercion + +Namespaces will be coerced in two ways: + +1. Casing will be coerced to `lowerCamelCase`. +2. Namespace length will be coerced to 4 segments. + +| Schema namespace (case-insensitive match) | Coerced namespace | +| ----------------------------------------- | ---------------------------------- | +| `microsoft.graph.MyNamespace` | `microsoft.graph.myNamespace` | +| `microsoft.graph.myNamespace.sub1.sub2` | `microsoft.graph.myNamespace.sub1` | + +## Namespace ownership + +Graph does not enforce namespace ownership. However, namespaces do have key contacts that should be consulted when modifying types in that namespace. + +Types must exist within a namespace, and workloads must explicitly state which namespace a type exists in. This does not mean that a workload must have their own namespace. A workload could continue to use their internal namespace or explicitly state that the types are in `microsoft.graph`. + +| Namespace | Owner | +| --------- | ----- | +| microsoft.graph.callRecords | [IC3 Records Distribution team](mailto:ic3recdist@microsoft.com) | + +## Extensibility and cross-referencing + +To extend a type in a different schema, a workload must declare that schema and the type in it. This is conceptually similar to .NET partial types. + +To reference a type in a different schema, simply refer to that type by fully qualified name (namespace + type name). + +OData fully supports cross-referencing and will not fail even if a cycle happens between schemas. That said, normal cyclical constraints apply - a cycle in inheritance will break things. + +## Managing multiple schemas + +Workloads must define schemas in their csdl using the Edmx format. [Microsoft.IC3.DataPlatform](https://microsoftgraph.visualstudio.com/onboarding/_git/AGS-OnboardingAutomationPipeline?path=%2FMicrosoft.IC3.DataPlatform.csdl&version=GBschemas%2Fprd%2Fbeta&_a=contents) is an example of a workload that exposes multiple namespaces. + +::: tip +As with schemas that exist in the `microsoft.graph` namespace, defining an `entity` type is optional, AGS will transform your schema to make all entity types derive from `microsoft.graph.entity`. +::: + +::: warning +Do not deviate from the general structure in the example below. GMM expects the XML structure (including xml namespace declarations) to match the example below. +::: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### Public `$metadata` + +The publicly hosted `$metadata` endpoint will have multiple schemas - one per coerced namespace. The primary entity container will exist in the schema with the `microsoft.graph` namespace. + +```xml + + + + + + + + + + + + + + + + ... + + + + + ... + + + + +``` From 04aa996c532d97dde67a33addefa537c0685a88e Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 26 Mar 2021 15:50:02 -0400 Subject: [PATCH 067/729] Initial commit of Graph guideline differences --- graph/Functions-and-actions.md | 90 +++ graph/WebHooks-architecture.svg | 215 ++++++ graph/WebHooks-vs.png | Bin 0 -> 64391 bytes .../adding-subtypes.md | 4 +- graph/advanced-patterns/README.md | 6 - graph/common-patterns/README.md | 13 - graph/{modeling-patterns => }/containment.md | 0 graph/{common-patterns => }/deltas.md | 58 +- .../{modeling-patterns => }/entity-complex.md | 8 +- .../evolvable-enums.md | 23 +- graph/gmm/README.md | 6 - graph/gmm/gmm-level-1.md | 88 --- graph/gmm/odata-conformance.md | 81 -- graph/guidelines.md | 725 +++++------------- .../long-running-operations.md | 26 +- graph/modeling-patterns/README.md | 6 - graph/modeling-patterns/shared-types.md | 18 - .../modeling-variants.md | 8 +- graph/naming-conventions.md | 119 +++ .../navigation-containment.md | 0 .../odata-types-primer.md | 0 .../omitting-properties.md | 37 +- graph/shared-types.md | 212 +++++ .../type-namespaces.md | 14 +- graph/{common-patterns => }/webhooks.md | 96 ++- 25 files changed, 965 insertions(+), 888 deletions(-) create mode 100644 graph/Functions-and-actions.md create mode 100644 graph/WebHooks-architecture.svg create mode 100644 graph/WebHooks-vs.png rename graph/{modeling-patterns => }/adding-subtypes.md (96%) delete mode 100644 graph/advanced-patterns/README.md delete mode 100644 graph/common-patterns/README.md rename graph/{modeling-patterns => }/containment.md (100%) rename graph/{common-patterns => }/deltas.md (75%) rename graph/{modeling-patterns => }/entity-complex.md (90%) rename graph/{modeling-patterns => }/evolvable-enums.md (89%) delete mode 100644 graph/gmm/README.md delete mode 100644 graph/gmm/gmm-level-1.md delete mode 100644 graph/gmm/odata-conformance.md rename graph/{advanced-patterns => }/long-running-operations.md (89%) delete mode 100644 graph/modeling-patterns/README.md delete mode 100644 graph/modeling-patterns/shared-types.md rename graph/{modeling-patterns => }/modeling-variants.md (94%) create mode 100644 graph/naming-conventions.md rename graph/{modeling-patterns => }/navigation-containment.md (100%) rename graph/{modeling-patterns => }/odata-types-primer.md (100%) rename graph/{advanced-patterns => }/omitting-properties.md (75%) create mode 100644 graph/shared-types.md rename graph/{modeling-patterns => }/type-namespaces.md (96%) rename graph/{common-patterns => }/webhooks.md (78%) diff --git a/graph/Functions-and-actions.md b/graph/Functions-and-actions.md new file mode 100644 index 00000000..945bd4c9 --- /dev/null +++ b/graph/Functions-and-actions.md @@ -0,0 +1,90 @@ +[[_TOC_]] + +# Functions and Actions + +The [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html), beside many other things, provides a way to describe the structure of the requests and responses of an OData service via the [Common Schema Definition Language](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html) (CSDL). + +## Functions + +The standard [defines](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Function) functions as: +>service-defined operations that MUST NOT have observable side effects and MUST return a single instance or collection of instances of any type. + +Please see the OData standard page to get more details on functions. + +### Example + +```XML + + + + +``` + +## Actions + +The standard [defines](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Action) actions as: +>service-defined operations that MAY have observable side effects and MAY return a single instance or a collection of instances of any type. + +Please see the OData standard for more details on actions. + +### Example + +```XML + + + + + +``` + +## Bound vs Unbound + +MS Graph does NOT support unbound actions or functions. + +Bound actions and functions are invoked on resources matching the type of the binding parameter. The binding parameter can be of any type, and it MAY be Nullable. For MS Graph, actions and functions must have the `isBound="true"` attribute. The first parameter is the binding parameter. + +## Overloads + +Both actions and functions support overloading, meaning a schema may contain multiple actions or functions with the same name. See OData [standard](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionOverloads) for more information and rules. + +## Parameters + +As Graph only supports bound actions and functions, all must have at least one parameter where the first is the binding parameter. The MUSTS of parameters: + +- Each parameter must have a simple identifier name. +- The parameter name must be unique within the overload. +- The parameter must specify a type. + +The overload rules as per the OData [standard](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionOverloads) apply when adding parameters to actions and functions. + +### Optional Parameters + +Graph supports the use of optional parameters. The optional parameter annotation can be used instead of creating function or action overloads when unnecessary. + +Example: +The `getNumber` function has an optional parameter `date` . You can use an optional parameter with this annotation: + +```XML + + + + + + + +``` + +instead of using an overload like this: + +```XML + + + + + + + + + + +``` \ No newline at end of file diff --git a/graph/WebHooks-architecture.svg b/graph/WebHooks-architecture.svg new file mode 100644 index 00000000..1f690100 --- /dev/null +++ b/graph/WebHooks-architecture.svg @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Page-1 + + + + Rectangle + Customer web service + + + + + + + + + + Customer web + service + + + + Rectangle.2 + Aggregator Service (graph.microsoft.com) + + + + + + + + + + Aggregator + + Service + + + (graph.microsoft.c + om) + + + + Rectangle.3 + Subscription Service + + + + + + + + + + Subscription + Service + + + + Rectangle.4 + Workload Service + + + + + + + + + + Workload Service + + + + Rectangle.5 + Publisher Service + + + + + + + + + + Publisher Service + + + + Dynamic connector.1000 + Public subscription + + + + + + + + Public subscription + + + + Dynamic connector.1001 + Public Subscription + + + + + + + + Public Subscription + + + + Dynamic connector.1002 + Workload subscription + + + + + + + + Workload subscription + + + + Dynamic connector.1003 + Emits Notifications + + + + + + + + Emits Notifications + + + + Dynamic connector.1004 + Batches and delivers notifications + + + + + + + + Batches and delivers notifications + + + + Dynamic connector.1005 + Validates subscription notification URL + + + + + + + + Validates subscription notification URL + + + + Dynamic connector.1006 + Gets subscription information + + + + + + + + Gets subscription information + + + + \ No newline at end of file diff --git a/graph/WebHooks-vs.png b/graph/WebHooks-vs.png new file mode 100644 index 0000000000000000000000000000000000000000..a844bc0979356310e3d5a0eec2912612c25cbaa4 GIT binary patch literal 64391 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 literal 0 HcmV?d00001 diff --git a/graph/modeling-patterns/adding-subtypes.md b/graph/adding-subtypes.md similarity index 96% rename from graph/modeling-patterns/adding-subtypes.md rename to graph/adding-subtypes.md index c33fb266..ffc07627 100644 --- a/graph/modeling-patterns/adding-subtypes.md +++ b/graph/adding-subtypes.md @@ -1,4 +1,4 @@ ---- +--- title: Adding subtypes owner: chrispre --- @@ -15,7 +15,7 @@ Table of Contents This article discusses the consequences of introducing a new sub-type in the Microsoft Graph schema for a type that are used in collections. -A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes (see [modeling variants](modeling-variants.md)). This allows to model collections of objects that have slightly different behavior. The common behavior is represented in the base type and the variations in a subtype, a concept very familiar from OO programming languages. It is straightforward to add a new subtype to the hierarchy with some consequences to the backwards compatibility as shown below. +A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes (see [modeling variants](Modeling-variants)). This allows to model collections of objects that have slightly different behavior. The common behavior is represented in the base type and the variations in a subtype, a concept very familiar from OO programming languages. It is straightforward to add a new subtype to the hierarchy with some consequences to the backwards compatibility as shown below. ## Overview diff --git a/graph/advanced-patterns/README.md b/graph/advanced-patterns/README.md deleted file mode 100644 index bae24b0b..00000000 --- a/graph/advanced-patterns/README.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Advanced API Patterns -owner: mastaffo ---- - -# Coming soon diff --git a/graph/common-patterns/README.md b/graph/common-patterns/README.md deleted file mode 100644 index ac6eb011..00000000 --- a/graph/common-patterns/README.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Common API Patterns -owner: vibiret ---- - -# Common patterns to implement in your API - -Great APIs provide consistent ways to address common problems. The common problems include: - -- The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph. This is addressed implementing [change notifications (aka webhooks)](./webhooks.md). -- The ability to track changes (pull) occuring in the data exposed by Microsoft Graph. This is addressed implementing [change tracking (aka delta query)](./deltas.md). - -As an API owner you should implement both those patterns to enable new scenarios for your APIs users but also to save COGS (reducing the need for apps to perform continuous polling on your API). diff --git a/graph/modeling-patterns/containment.md b/graph/containment.md similarity index 100% rename from graph/modeling-patterns/containment.md rename to graph/containment.md diff --git a/graph/common-patterns/deltas.md b/graph/deltas.md similarity index 75% rename from graph/common-patterns/deltas.md rename to graph/deltas.md index 9399f21a..dd5a9653 100644 --- a/graph/common-patterns/deltas.md +++ b/graph/deltas.md @@ -1,7 +1,4 @@ ---- -title: Adding support for delta queries -owner: vibiret ---- +[[_TOC_]] # Adding support for delta queries @@ -10,9 +7,9 @@ Delta query enables application to discover newly created, updated, or deleted e ## Why should you add delta query support for your entities? There are different scenarios where customers are looking at syncing data to a separate system and/or tracking changes in a non-lossy way (making sure they are not missing any changes). These scenarios include compliance solutions, DLP solutions, apps that need to support offline usage and many more. -Today, if your API surface does not support delta queries, the only avenue for customers to implement such scenarios is by **continuously query your API surface**. This increases the complexity and cost of such solutions for customers or makes implementing certain scenarios impossible at scale. More importantly, it greatly increases COGS for the Microsoft Graph as well as your API. +Today, if your API surface does not support delta queries, the only avenue for customers to implement such scenarios is to **continuously query your API surface**. This increases the complexity and cost of such solutions for customers or makes implementing certain scenarios impossible at scale. More importantly, it greatly increases COGS for the Microsoft Graph as well as your API. -The Microsoft Identity Platform (AAD) has implemented delta query support for a majority of it's entities and will continue to deliver more delta query support in an effort to provide a better experience for customers but also to **decrease COGS**. +The Microsoft Identity Platform (AAD) has implemented delta query support for a majority of its entities and will continue to deliver more delta query support in an effort to provide a better experience for customers but also to **decrease COGS**. ## How to add support for delta queries in your API @@ -28,12 +25,12 @@ Both the API review process and the API schema modification process will require There are a few places where the reference of a new delta query support must be inserted. 1. You need to add your resource to the [table of supported resources](https://docs.microsoft.com/en-us/graph/delta-query-overview#supported-resources) -1. You need to state that your resource supports delta query in the abstract, eg [orgContact](https://docs.microsoft.com/en-us/graph/api/resources/orgcontact?view=graph-rest-1.0). (\*) -1. You need to add a delta query support page for the resource in the api reference eg [orgContact delta](https://docs.microsoft.com/en-us/graph/api/orgcontact-delta?view=graph-rest-1.0&tabs=http). (\*) -1. You need to add reference to any page you added in the coresponding Table Of Content. -1. You need to add an entry for each version/entity that supports Delta queries to the [changelog](https://docs.microsoft.com/en-us/graph/changelog), [guidance](../../document/guidelines/changelog.html) +1. You need to state that your resource supports delta query in the abstract, e.g. [orgContact](https://docs.microsoft.com/en-us/graph/api/resources/orgcontact?view=graph-rest-1.0). (\*) +1. You need to add a delta query support page for the resource in the api reference e.g. [orgContact delta](https://docs.microsoft.com/en-us/graph/api/orgcontact-delta?view=graph-rest-1.0&tabs=http). (\*) +1. You need to add reference to any page you added in the corresponding Table Of Content. +1. You need to add an entry for each version/entity that supports Delta queries to the [changelog](https://docs.microsoft.com/en-us/graph/changelog), [guidance](../../Document/Guidelines/Changelog) -> \*: These pages are available for beta and v1.0, make sure you update the beta pages during the public preview of change notifications support for your API. Make sure you update v1.0 pages when support ships for general availability. Updates for different versions can be done in different pull request. +> **Note**: These pages are available for beta and v1.0, make sure you update the beta pages during the public preview of change notifications support for your API. Make sure you update v1.0 pages when support ships for general availability. Updates for different versions can be done in different pull request. You can see an example of adding delta query support for both v1.0 and beta to the docs [here](https://github.com/microsoftgraph/microsoft-graph-docs/pull/7451). @@ -43,9 +40,9 @@ Should you require assistance with the documentation process, you can contact th ### Submit your API for review -Now that you have pre-requisite items, you are ready to submit an [API review](../../review/final-prep.html). Describe that you are adding support for delta query (function) for your API. +Now that you have prerequisite items, you are ready to submit an [API review](../../Review/Create-an-API-proposal). Describe that you are adding support for delta query (function) for your API. -> Note: if you are adding a net new API with Delta query support on day one, you can reuse the existing API onboarding review so long as the delta query support was included in the initial API review +> **Note**: if you are adding a net new API with Delta query support on day one, you can reuse the existing API onboarding review so long as the delta query support was included in the initial API review ### Update the schema metadata @@ -76,9 +73,9 @@ In your entity declaration, you need to add an annotation stating that the entit ``` -You can now submit the schema changes following the [guidance](https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Partners/55110/Test-Using-VSTS-Repo). +You can now submit the schema changes following the [guidance](../../../Rollout/Publish-schema/Test-using-VSTS-repo). -> Note: if you want the delta capability to be hidden from publicly available APIs for testing reasons, you can leverage [Privileged identities](https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Partners/55117/Privileged-Api) by setting the `ags:IsHidden="true"` attribute on both the annotation and the function. +> Note: if you want the delta capability to be hidden from publicly available APIs for testing reasons, you can leverage [Privileged identities](../../../Rollout/Publish-schema/Publish-a-test-endpoint/Privileged-API) by setting the `ags:IsHidden="true"` attribute on both the annotation and the function. ### Provide required information to support @@ -106,16 +103,15 @@ Some of the information that support will require to add it to their internal do You need to implement a query response to the `/microsoft.graph.delta` (also aliased `/delta`) requests that the Aggregator Gateway Service (AGS, the service immediately behind graph.microsoft.com) will forward to your workload's API. > The delta query endpoint should match the following pattern to avoid requiring code changes for request transformation in the AGS: `/{version}/entity/delta`. - > The delta query endpoint should authorize on the same permissions required to enumerate the entity type. The [OData ASP.NET](https://www.nuget.org/packages/Microsoft.AspNet.WebApi.OData/) and [OData ASP.NET core](https://www.nuget.org/packages/Microsoft.AspNetCore.OData) provide base controllers, serialization wrappers and more that help you generate the delta query response as shown [in the public documentation](https://docs.microsoft.com/en-us/odata/webapi/deltafeed_support). ### Update routing information -If your delta query implemention does not live on the same service (FQDN) as your entity's API, you need to update the endpoint routing configuration to account for it. For more information, see [Gradual Config Rollout ACIS Operations](https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Partners/40069/Gradual-Config-Rollout-ACIS-Operations). +If your delta query implementation does not live on the same service (FQDN) as your entity's API, you need to update the endpoint routing configuration to account for it. For more information, see [Gradual Configuration Rollout ACIS Operations](../../../Rollout/Publish-config/Gradual-config-rollout-ACIS-operations)/Gradual-config-rollout-ACIS-operations). -You also need to update your workload configuration to indicate your support the delta sync protocol eg: +You also need to update your workload configuration to indicate your support the delta sync protocol e.g.: ```xml @@ -143,7 +139,7 @@ When a nextLink is returned, you must not return a deltaLink. The nextLink will 1. Know more changes must be enumerated. 1. Query the next changes. -When querying the the public nextLink, the AGS will decode the nextLink (value of \$skiptoken query parameter) that was provided by the workload and provide the decoded value as a query parameter named `nextLink` to the workload when forwarding the request. +When querying the public nextLink, the AGS will relay the nextLink (value of \$skiptoken query parameter) that was provided by the workload and provide the value as a query parameter named `nextLink` to the workload when forwarding the request. When all changes are enumerated, a @odata.deltaLink property should be attached to the response object. @@ -155,41 +151,39 @@ If a response to a delta query request is returning the last change available at When a deltalink is returned, you must not return a nextLink. It signals to the client that all the current changes have been enumerated and that they should query back, with the deltaLink, at a later time. The deltaLink will be encoded and prefixed by the public Microsoft Graph URL automatically by the AGS before being returned to the client. -When querying the the public deltaLink, the AGS will decode the deltaLink (value of \$deltatoken query parameter) that was provided by the workload and provide the decoded value as a query parameter named `deltaLink` to the workload when forwarding the request. +When querying the public deltaLink, the AGS will decode the deltaLink (value of \$deltatoken query parameter) that was provided by the workload and provide the decoded value as a query parameter named `deltaLink` to the workload when forwarding the request. > Delta links and next links should not exceed 10k characters to avoid routing issues at the AGS level. -### Handle filter, top and select query paremeters +### Handle filter, top and select query parameters -You should consider how these Odata query parameters may or may not be supported for optimizing the response: +You should consider how these OData query parameters may or may not be supported for optimizing the response: - **\$select**: you **must** support the select query parameter to client applications to filter which properties they'd like to get in the response. - **\$filter**: you may support the filter query parameter to allow client applications to filter which objects they'd like to get from the response. - **\$top**: you may support the top query parameter to allow client applications to customize the number of results they'd like to get per page in the response. (see nextLink) - **\$expand**: you may support the expand query parameter to allow client applications to get additional linked entities they get in the response. -- **\$oderby**: you may support the orderby query parameter to allow client applications to customize the order of the results they get in the response. -- **\$skip**: you should **not** support the skip query parameter as it's behavior might conflict with with nextLink behavior already madated by delta query. +- **\$orderby**: you may support the orderby query parameter to allow client applications to customize the order of the results they get in the response. +- **\$skip**: you should **not** support the skip query parameter as it's behavior might conflict with nextLink behavior already mandated by delta query. - **\$count**: you may support the count query parameter to allow clients applications to get the count of items in the response along with the results. - **\$search**: you may support the search query parameter to allow clients applications to filter which objects they'd like to get from the response. -- **\$format**: you should **not** support the format query parameter as the AGS is doing some data parsing and replacement before returing the response to clients applications and supports limited formats (JSON). - -> If you are building your API using ASP.NET MVC or ASP.NET core MVC you can leverage the OData library to parse and apply oData query paremeters to the delta feed. Make sure you [configure your service pipeline and add the enablequery tag](https://docs.microsoft.com/en-us/odata/webapi/first-odata-api). The library also allows you to [advertise non-support of some query parameters on object properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnet.odata.query.nonfilterableattribute?view=odata-aspnetcore-7.0) via attributes. +- **\$format**: you should **not** support the format query parameter as the AGS is doing some data parsing and replacement before returning the response to clients applications and supports limited formats (JSON). +> If you are building your API using ASP.NET MVC or ASP.NET core MVC you can leverage the OData library to parse and apply oData query parameters to the delta feed. Make sure you [configure your service pipeline and add the enablequery tag](https://docs.microsoft.com/en-us/odata/webapi/first-odata-api). The library also allows you to [advertise non-support of some query parameters on object properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnet.odata.query.nonfilterableattribute?view=odata-aspnetcore-7.0) via attributes. > If you want more control over OData query options, you can additionally leverage the [ODataQueryOptions object](https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#invoking-query-options-directly). - -> Odata query parameters will be added in the encoded delta/skip token provided to the client application so they do not have to add it to each request. They parameters will be decoded and provided to your workload API by the AGS in any subsequent request. Updating the OData query parameters is not supported after an initial delta/skip token has been generated. The parameters stay consistent over time or the client application must restart the synchronization from scratch with the new parameters (querying delta API with no delta/skip token). +> OData query parameters will be added in the encoded delta/skip token provided to the client application so they do not have to add it to each request. The parameters will be decoded and provided to your workload API by the AGS in any subsequent request. Updating the OData query parameters is not supported after an initial delta/skip token has been generated. The parameters stay consistent over time or the client application must restart the synchronization from scratch with the new parameters (querying delta API with no delta/skip token). ## How to get help Should you need any help during your design and implementation, there are a couple of ways you can reach out: - [Stackoverflow.com](https://stackoverflow.com): for any question that does not contain confidential, internal or customer related information. Example: questions about ASP.NET core MVC, questions about the OData libraries, etc. -- [Internal Stackoverflow](https://stackoverflow.microsoft.com): for any question that might contain confidential or internal information. Example: how do I configure the routing in AGS to do... -- [Teams: Microsoft Graph > Delta query](https://teams.microsoft.com/l/channel/19%3a32dabdaf736a4c9482fc3d96967b2ea8%40thread.skype/Delta%2520query?groupId=6d279915-6f5d-452c-895b-7f4f82038843&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47): for any question that contains details really specific to your implementation, escalation of questions left unanswered on one of the stackoverflow platforms, ... +- [Internal StackOverflow](https://stackoverflow.microsoft.com): for any question that might contain confidential or internal information. Example: how do I configure the routing in AGS to do... +- [Teams: Microsoft Graph > Delta query](https://teams.microsoft.com/l/channel/19%3a32dabdaf736a4c9482fc3d96967b2ea8%40thread.skype/Delta%2520query?groupId=6d279915-6f5d-452c-895b-7f4f82038843&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47): for any question that contains details really specific to your implementation, escalation of questions left unanswered on one of the StackOverflow platforms, ... Ask: please refrain from asking questions directly to the engineering team via either emails, Teams chat etc... This does not scale and it doesn't capture the question (and answer) for other people that might have the same question as you. You should always try to ask a question on stack overflow first before reaching out on the Teams channel. ## End to end testing -You can use canary and ppe for end to end testing of the implementation as outlined in [the following documentation](https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Partners/55110/Test-Using-VSTS-Repo). +You can use Canary and PPE for end to end testing of the implementation as outlined in [the following documentation](../../../Rollout/Publish-schema/Test-using-VSTS-repo)/Rollout/Final-steps/Test-using-VSTS-repo). Private previews can be done by creating a _new version_ of Microsoft Graph as outlined in the documentation previously linked. diff --git a/graph/modeling-patterns/entity-complex.md b/graph/entity-complex.md similarity index 90% rename from graph/modeling-patterns/entity-complex.md rename to graph/entity-complex.md index f723ce31..f853cbc6 100644 --- a/graph/modeling-patterns/entity-complex.md +++ b/graph/entity-complex.md @@ -1,4 +1,4 @@ ---- +--- title: Entity Types and Complex Types --- @@ -28,7 +28,7 @@ Complex Types are non-scalar properties of entity types that enable scalar prope - A complex type doesn't have keys and therefore cannot exist independently. - Complex type can only exist as properties of entity types or other complex types. -- It cannot participate in relationships (see navigation properties) directly. +- As of 9/2020, navigation properties within complex types are supported in Microsoft Graph (with some limitations, such as: cannot be composed with operations, and structural properties cannot be used within `$expand` paths). ## Comparison @@ -51,7 +51,7 @@ In the example below, we have added an Author as an Entity Type and Address a Co ``` -You can see that Address type does not have any sort of key property. Complex types cannot be tracked on their own, so as a property in the Author class, it will be **tracked as part of** an author object. The consequence is that its lifecycle is coupled to the enclosing Entity Type: When the author gets deleted, the address gets deleted as well. +You can see that Address type does not have any sort of key property. Complex types cannot be tracked on their own, so as a property in the Author class, it will be **tracked as part of** an author object. The consequence is that its life cycle is coupled to the enclosing Entity Type: When the author gets deleted, the address gets deleted as well. ## Summary @@ -59,4 +59,4 @@ In Summary: - Both Entity Types and Complex types are named types that declare a list of properties for the objects of that type. - An Entity Type always has a key declared whereas a Complex type doesn't. -- Objects of an Entity Type can be directly addressed via an URL but ComplexTypes are always contained in an EntityType object and can only be addressed through a combination of an Entity address and a property name. (see more at [Navigation Properties and Containment](navigation-containment.md)) +- Objects of an Entity Type can be directly addressed via an URL but ComplexTypes are always contained in an EntityType object and can only be addressed through a combination of an Entity address and a property name. diff --git a/graph/modeling-patterns/evolvable-enums.md b/graph/evolvable-enums.md similarity index 89% rename from graph/modeling-patterns/evolvable-enums.md rename to graph/evolvable-enums.md index 4547ffd4..7c28b6ea 100644 --- a/graph/modeling-patterns/evolvable-enums.md +++ b/graph/evolvable-enums.md @@ -1,7 +1,4 @@ ---- -title: Evolvable enums -owners: sanonsen, mastaffo ---- +[[_TOC_]] # Adding Members to Enumerations @@ -13,11 +10,11 @@ An evolvable enumeration contains the sentinel member `unknownFutureValue` after ```xml - - + + ... - - + + ``` @@ -38,12 +35,12 @@ The new enumeration member `newday` is added after `unknownFutureValue`: ```xml - - + + ... - - - + + + ``` diff --git a/graph/gmm/README.md b/graph/gmm/README.md deleted file mode 100644 index ce1f502d..00000000 --- a/graph/gmm/README.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Design rules -owner: mastaffo ---- - -# Coming soon diff --git a/graph/gmm/gmm-level-1.md b/graph/gmm/gmm-level-1.md deleted file mode 100644 index 00f5c249..00000000 --- a/graph/gmm/gmm-level-1.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: GMM level 1 -owner: mastaffo ---- - -# GMM level 1 - -## Naming - -| Id | Name | Severity | -| ----------------------- | ---------------------------------------------------------------------------------------------- | -------- | -| N0001 | ✔ DO use `lowerCamelCase` for _all_ names. | Error | -| N0002 | ✔ DO use singular nouns for type names and properties with cardinality `1`. | Error | -| N0003 | ✔ DO use plural nouns for collections and properties with cardinality `*`. | Error | -| N0004 | ✖ AVOID using brand names in type or property names. | Error | -| N0005 | ✖ AVOID using acronyms or abbreviations unless the abbreviation is extremely well known. | Error | -| N0006 | ✔ DO case two-letter acronyms with the same case. | Error | -| N0007 | ✔ DO case three+ letter acronyms the same as a normal word. | Error | -| N0008 | ✖ DO NOT suffix property names with primitive type information unless the type is temporal. | Error | -| N0009 | ✔ DO suffix `Edm.Date` property names with `…Date`. | Error | -| N0010 | ✔ DO suffix `Edm.Time` property names with `…Time`. | Error | -| N0011 | ✔ DO suffix `Edm.DateTime` property names with `…DateTime`. | Error | -| N0012 | ✖ DO NOT prefix properties with the name of the type unless it is significantly more readable. | Error | -| N0013 | ✔ CONSIDER using a property name from the shared property name list. | Info | -| N0014 | ✖ AVOID using `type` as the name of a property. | Warning | -| N0015 | ✖ AVOID using `Mail` in property names. Instead use `Email`. | Warning | - -## Modeling - -| Id | Name | Severity | -| ----------------------- | ----------------------------------------------------------------------------------------------- | -------- | -| M0001 | ✔ DO make the key of every entity a single property with name `id` and type `Edm.String`. | Error | -| M0002 | ✔ DO make every entity inherit from `microsoft.graph.entity`. | Error | -| M0003 | ✖ DO NOT use parallel collections; use collections of complex types instead. | Error | -| M0004 | ✔ CONSIDER use a proper collection rather than `property1`, `property2`, etc. | Warning | -| M0005 | ✖ AVOID denormalization unless it is necessary for developer scenarios. | Warning | -| M0006 | ✔ CONSIDER using an evolvable enumeration for enumerations that will add members in the future. | Warning | -| M0007 | ✖ AVOID operations such as actions and functions whenever possible. | Warning | -| M0008 | ✔ CONSIDER using inheritance when types share three or more properties in common. | Warning | -| M0009 | ✖ DO NOT use a complex type with an `id`. Instead use an entity. | Error | -| M0010 | ✔ CONSIDER making enums with two options a boolean. | Warning | -| M0011 | ✔ CONSIDER making enums with has flags be a power of two. | Warning | -| M0012 | ✖ DO NOT have entity types override their base properties. | Error | -| M0013 | ✖ DO NOT have complex types override their base properties. | Error | -| M0014 | ✔ DO have valid navigation properties for entity sets. | Error | -| M0015 | ✔ DO have valid navigation properties for singletons. | Error | - -## Request patterns - -| Id | Name | Severity | -| ----------------------- | ---------------------------------------------------------------------------------------------- | -------- | -| H0001 | ✔ DO use `GET …/{collection}` and `GET …/{collection}/{id}` for listing and reading resources. | Error | -| H0002 | ✔ DO use `POST …/{collection}` for creating resources. | Error | -| H0003 | ✔ DO use `PATCH …/{collection}/{id}` for updating resources. | Error | -| H0004 | ✖ AVOID using `PUT …/{collection}/{id}` for updating resources. | Warning | -| H0005 | ✖ DO NOT use `PATCH` to replaces resources or `PUT` to partially update resources. | Error | -| H0006 | ✖ AVOID patterns that require multiple round trips to complete a single logical action. | Warning | -| H0007 | ✔ CONSIDER supporting `return`, `omit-nulls`, and `include-evolvable-enums` preferences. | Warning | -| H0008 | ✔ DO make requests and responses symmetrical. | Error | - -## Serialization - -| Id | Name | Severity | -| ----------------------- | ---------------------------------------------------------------------- | -------- | -| S0001 | ✔ DO use an object as the root of all JSON payloads. | Error | -| S0002 | ✔ DO use a `value` property in the root object to return a collection. | Error | -| S0003 | ✔ DO return a `@odata.context` URL on all responses. | Error | -| S0004 | ✔ DO include `@odata.type` annotations when the type is ambiguous. | Warning | -| S0005 | ✔ DO return JSON by default. | Error | -| S0006 | ✔ DO minify responses. | Info | - -## Authorization - -| Id | Name | Severity | -| ----------------------- | -------------------------------------------------------------------------- | -------- | -| A0001 | ✖ DO NOT use a scope ending with `.Read` to authorize a data modification. | Error | -| A0002 | ✔ DO use `POST …/{collection}` for creating resources. | Error | -| A0003 | ✔ DO use `PATCH …/{collection}/{id}` for updating resources. | Error | -| A0004 | ✖ AVOID using `PUT …/{collection}/{id}` for updating resources. | Warning | - -## Errors - -| Id | Name | Severity | -| ----------------------- | ----------------------------------------------------------------------------------------------- | -------- | -| E0001 | ✔ DO return an `error` property with a child `code` property in all error responses. | Error | -| E0002 | ✔ DO return a `403 Forbidden` error when insufficient scopes are present on the auth token. | Error | -| E0003 | ✔ CONSIDER returning a `404 Not found` error if a `403` would result in information disclosure. | Error | -| E0004 | ✔ DO return a `429 Too many requests` error when the caller has exceeded throttling limits. | Error | diff --git a/graph/gmm/odata-conformance.md b/graph/gmm/odata-conformance.md deleted file mode 100644 index c383420b..00000000 --- a/graph/gmm/odata-conformance.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: OData conformance ---- - -# OData Conformance Requirements for Microsoft Graph - -The conformance requirements are listed below in different levels. These levels correspond to whether a service must implement them or if they are optional. The actual OData conformance requirements and details (sections mentioned beside the point) can be found at this [**link.**](http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part1-protocol/odata-v4.0-errata03-os-part1-protocol-complete.html#_Toc453752324) - -## Level 1 - -Minimum Requirements a workload service needs to meet in order to onboard onto graph successfully. - -- MUST return data according to at least one of the OData defined formats (section 7). We require JSON -- MUST support server-driven paging when returning partial results (section 11.2.5.7) -- MUST successfully parse the request according to [OData-ABNF][2] for any supported system query string options. -- MUST expose only data types defined in [OData-CSDL][2] -- MUST NOT violate any OData update semantics (section 11.4 and all subsections) -- MUST publish metadata at $metadata according to [OData-CSDL][2] (section 11.1.2) -- MUST support the resource path conventions defined in [OData URL][2] - -## Level 2 - -These requirements are highly recommended, but are still optional. This may vary based on the workload service, where some conformance requirements must be implemented. - -- Return the appropriate OData-Version header (section 8.1.5) -- Conform to the semantics the following headers, or fail the request - - Accept (section 8.2.1) - - OData-MaxVersion (section 8.2.7) -- Include edit links (explicitly or implicitly) for all updatable or deletable resources according to [OData-Atom][2] and [OData-JSON][2] -- Support POST of new entities to insertable entity sets (section 11.4.1.5 and 11.4.2.1) -- Support PATCH to all edit URLs for updatable resources (section 11.4.3) -- Support DELETE to all edit URLs for deletable resources (section 11.4.5) -- Support DELETE to $ref to remove an entity from an updatable navigation property (section 11.4.6.2) -- Return a Location header with the edit URL or read URL of a created resource (section 11.4.1.5) -- Support $select (section11.2.4.1) -- Support casting to a derived type according to [OData URL][2] if derived types are present in the model -- Support $top (section 11.2.5.3) -- Support $filter (section 11.2.5.1) -- Support eq, ne filter operations on properties of entities in the requested entity set (section 11.2.5.1.1) -- Support the $skip system query option (section 11.2.5.4) -- Support the $count system query option (section 11.2.5.5) -- Support $orderby asc and desc on individual properties (section 11.2.5.2) - -## Level 3 - -These Odata conformance requirements can be implemented based on the discretion/requirements of the workload. To decide whether a workload service needs to implement one or more these, please contact Microsoft Graph team. - -- Support $expand (section 11.2.4.2) -- Support POST of new related entities to updatable navigation properties (section 11.4.6.1) -- Support POST to $ref to add an existing entity to an updatable related collection (section 11.4.6.1) -- Support PUT to $ref to set an existing single updatable related entity (section 11.4.6.3) -- Support if-match header in update/delete of any resources returned with an ETag (section 11.4.1.1) -- Include the OData-EntityId header in response to any create or upsert operation that returns 204 No Content (Section 8.3.3) -- Support Upserts (section 11.4.4) -- Support PUT and PATCH to an individual primitive (section 11.4.9.1) or complex (section 11.4.9.3) property (respectively) -- Support DELETE to set an individual property to null (section 11.4.9.2) -- Support deep inserts (section 11.4.2.2) -- Support /$value on media entities (section 4.10. in [OData URL][2] and individual properties (section 11.2.3.1) -- Support aliases in $filter expressions (section 11.2.5.1.3) -- Support additional filter operations (section 11.2.5.1.1) and MUST return 501 Not Implemented for any unsupported filter operations (section 9.3.1) -- Support the canonical functions (section 11.2.5.1.2) and MUST return 501 Not Implemented for any unsupported canonical functions (section 9.3.1) -- Support $filter on expanded entities (section 11.2.4.2.1) -- Support the $search system query option (section 11.2.5.6) -- Support $expand (section 11.2.4.2) -- Support the lambda operators any and all on navigation- and collection-valued properties (section 5.1.1.5 in [OData URL][2]) -- Support $expand (section 11.2.4.2) - - Support returning references for expanded properties (section 11.2.4.2) - - Support $filter on expanded entities (section 11.2.4.2.1) - - Support cast segment in expand with derived types (section 11.2.4.2.1) - - Support $orderby asc and desc on individual properties (section 11.2.4.2.1) - - Support the $count system query option for expanded properties (section 11.2.4.2.1) - - Support $top and $skip on expanded properties (section 11.2.4.2.1) - - Support $search on expanded properties (section 11.2.4.2.1) - - Support $levels for recursive expand (section 11.2.4.2.1.1) -- Support batch requests (section11.7 and all subsections) -- Support Asynchronous operations (section 8.2.8.8) -- Support Delta change tracking (section 8.2.8.6) -- Support cross-join queries defined in [OData URL][2] -- Support a conforming OData service interface over metadata (section 11.1.3) - -[2]: http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part1-protocol/odata-v4.0-errata03-os-part1-protocol-complete.html#ABNF diff --git a/graph/guidelines.md b/graph/guidelines.md index a6525ee8..d37a939f 100644 --- a/graph/guidelines.md +++ b/graph/guidelines.md @@ -1,52 +1,209 @@ ---- -title: Design your API -owner: mastaffo ---- - -# Design your API - - - -> "Make the API to your library as boring as possible. You want the functionality to be interesting, not the API." -- Chris Sells, [Framework Design Guidelines][fdg] - -API design is a crucial but often overlooked aspect of the API development process. - -## Your most important investment - -The design of your API is arguably the most important investment you will make in it. The design of your API is what creates the first impression for developers. - -We want to provide our developers an incredible promise: no matter where they are in Microsoft Graph, the API style should feel familiar. Naming, casing, filtering, pagination, and more are handled the same way through all Graph APIs. - -Consistency is not cheap. This principle is so important that it is listed as the second quality of a well-designed framework in [Framework Design Guidelines](https://www.safaribooksonline.com/library/view/framework-design-guidelines/9780321545671/chapter01.html#ch1): - -> Good framework design does not happen magically. It is hard work that consumes lots of time and resources. If you are not willing to invest real money in the design, you should not expect to create a well-designed framework. - -## Design framework - -Microsoft Graph has a well-defined framework for API design. On the positive side, this well-defined framework reduces [bikeshedding](https://en.wikipedia.org/wiki/Law_of_triviality) and results in a better experience for developers who consume Graph. On the negative side, it's harder for you. You have to learn about how we do API design, and you have less "freedom" in how you design your API. - -Even so, this work results in a clean and simple-to-use experience for our users. We believe 100% that this is the right thing to do for our customers, and we hope you do too. - -| Section | Description | -| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Basic guidance](/add/design/basic-guidance/) | Beginners should start with [basic guidance](/add/design/basic-guidance/), where we provide an overview of the design guidelines that inform our API design. We also provide a recommended approach for people who are new to API design. | -| [Key principles](/add/design/key-principles/) | [Key principles](/add/design/key-principles/) are our non-negotiables. These are the naming, authentication, throttling, and error guidelines that every API must adhere to. | -| [Modeling patterns](/add/design/modeling-patterns/) | We introduce, compare, and contrast different ways to model the schema of your API in [modeling patterns](/add/design/modeling-patterns/). This includes some special things we've introduced, such as [evolvable enumerations](/add/design/modeling-patterns/evolvable-enums.md), so don't miss it! | -| [Common patterns](/add/design/common-patterns/) | The most encouraged API patterns are documented in [common patterns](/add/design/common-patterns/). Common patterns include things we would like to see every API support, such as Webhooks and deltas. | -| [Advanced patterns](/add/design/advanced-patterns/) | Less common patterns are listed in [advanced patterns](/add/design/advanced-patterns/). | -| [Design rules](/add/design/gmm/) | The detailed list of rules enforced by our tooling and API reviewers is available in [GMM](/add/design/gmm/). This is primarily reference content and not something we expect you to read through as you are ramping up. | - -## Section Summary - -Overall, the design section exists to introduce and educate you on the best practices for building and adding your new API to Microsoft Graph. By the end of this section, you should have a working CDSL file, should understand the REST, Odata, and GMM guidelines well enough to work with the GMM testing tool, and be ready to proceed to the review and build stages with a well-designed and though-out architecture. - -## Additional resources +# Graph API Design API Patterns + +## Introduction + +The Graph REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Graph guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Graph-specific guidance and additional details. + +The following table of contents links back to primary guidelines where there are no differences in Graph guidelines. Where differences exist, the section heading is **bold**. + +## 2. Table of contents + + +- [Microsoft REST API Guidelines Working Group](../guidelines#microsoft-rest-api-guidelines-working-group) +- [1. Abstract](../guidelines#1-abstract) +- [2. Table of contents](../guidelines#2-table-of-contents) +- [3. Introduction](../guidelines#3-introduction) + - [3.1. Recommended reading](../guidelines#31-recommended-reading) +- [4. Interpreting the guidelines](../guidelines#4-interpreting-the-guidelines) + - [4.1. Application of the guidelines](../guidelines#41-application-of-the-guidelines) + - [4.2. Guidelines for existing services and versioning of services](../guidelines#42-guidelines-for-existing-services-and-versioning-of-services) + - [4.3. Requirements language](../guidelines#43-requirements-language) + - [4.4. License](../guidelines#44-license) +- [5. Taxonomy](../guidelines#5-taxonomy) + - [5.1. Errors](../guidelines#51-errors) + - [5.2. Faults](../guidelines#52-faults) + - [5.3. Latency](../guidelines#53-latency) + - [5.4. Time to complete](../guidelines#54-time-to-complete) + - [5.5. Long running API faults](../guidelines#55-long-running-api-faults) +- [6. Client guidance](../guidelines#6-client-guidance) + - [6.1. Ignore rule](../guidelines#61-ignore-rule) + - [6.2. Variable order rule](../guidelines#62-variable-order-rule) + - [6.3. Silent fail rule](../guidelines#63-silent-fail-rule) +- [7. Consistency fundamentals](../guidelines#7-consistency-fundamentals) + - [7.1. URL structure](../guidelines#71-url-structure) + - [7.2. URL length](../guidelines#72-url-length) + - [7.3. Canonical identifier](../guidelines#73-canonical-identifier) + - [7.4. Supported methods](../guidelines#74-supported-methods) + - [7.4.1. POST](../guidelines#741-post) + - [7.4.2. PATCH](../guidelines#742-patch) + - [7.4.3. Creating resources via PATCH (UPSERT semantics)](../guidelines#743-creating-resources-via-patch-upsert-semantics) + - [7.4.4. Options and link headers](../guidelines#744-options-and-link-headers) + - [7.5. Standard request headers](../guidelines#75-standard-request-headers) + - [7.6. Standard response headers](../guidelines#76-standard-response-headers) + - [7.7. Custom headers](../guidelines#77-custom-headers) + - [7.8. Specifying headers as query parameters](../guidelines#78-specifying-headers-as-query-parameters) + - [7.9. PII parameters](../guidelines#79-pii-parameters) + - [7.10. Response formats](../guidelines#710-response-formats) + - [7.10.1. Clients-specified response format](../guidelines#7101-clients-specified-response-format) + - [7.10.2. Error condition responses](../guidelines#7102-error-condition-responses) + - [7.11. HTTP Status Codes](../guidelines#711-http-status-codes) + - [7.12. Client library optional](../guidelines#712-client-library-optional) +- [8. CORS](../guidelines#8-cors) + - [8.1. Client guidance](../guidelines#81-client-guidance) + - [8.1.1. Avoiding preflight](../guidelines#811-avoiding-preflight) + - [8.2. Service guidance](../guidelines#82-service-guidance) +- [9. Collections](../guidelines#9-collections) + - [9.1. Item keys](../guidelines#91-item-keys) + - [9.2. Serialization](../guidelines#92-serialization) + - [9.3. Collection URL patterns](../guidelines#93-collection-url-patterns) + - [9.3.1. Nested collections and properties](../guidelines#931-nested-collections-and-properties) + - [9.4. Big collections](../guidelines#94-big-collections) + - [9.5. Changing collections](../guidelines#95-changing-collections) + - [9.6. Sorting collections](../guidelines#96-sorting-collections) + - [9.6.1. Interpreting a sorting expression](../guidelines#961-interpreting-a-sorting-expression) + - [9.7. Filtering](../guidelines#97-filtering) + - [9.7.1. Filter operations](../guidelines#971-filter-operations) + - [9.7.2. Operator examples](../guidelines#972-operator-examples) + - [9.7.3. Operator precedence](../guidelines#973-operator-precedence) + - [9.8. Pagination](../guidelines#98-pagination) + - [9.8.1. Server-driven paging](../guidelines#981-server-driven-paging) + - [9.8.2. Client-driven paging](../guidelines#982-client-driven-paging) + - [9.8.3. Additional considerations](../guidelines#983-additional-considerations) + - [9.9. Compound collection operations](../guidelines#99-compound-collection-operations) +- [**9a. Resource Design**](#9a-resource-design) + - [**9a.1. Noun Resources**](#9a1-noun-resources) + - [**9a.2. Verb Resources**](#9a2-verb-resources) + - [**9a.3. Resource Modeling**](#9a3-resource-modeling) +- [10. Delta queries](#10-delta-queries) + - [10.1. Delta links](../guidelines#101-delta-links) + - [10.2. Entity representation](../guidelines#102-entity-representation) + - [10.3. Obtaining a delta link](../guidelines#103-obtaining-a-delta-link) + - [10.4. Contents of a delta link response](../guidelines#104-contents-of-a-delta-link-response) + - [10.5. Using a delta link](../guidelines#105-using-a-delta-link) +- [11. JSON standardizations](../guidelines#11-json-standardizations) + - [11.1. JSON formatting standardization for primitive types](../guidelines#111-json-formatting-standardization-for-primitive-types) + - [11.2. Guidelines for dates and times](../guidelines#112-guidelines-for-dates-and-times) + - [11.2.1. Producing dates](../guidelines#1121-producing-dates) + - [11.2.2. Consuming dates](../guidelines#1122-consuming-dates) + - [11.2.3. Compatibility](../guidelines#1123-compatibility) + - [11.3. JSON serialization of dates and times](../guidelines#113-json-serialization-of-dates-and-times) + - [11.3.1. The `DateLiteral` format](../guidelines#1131-the-dateliteral-format) + - [11.3.2. Commentary on date formatting](../guidelines#1132-commentary-on-date-formatting) + - [11.4. Durations](../guidelines#114-durations) + - [11.5. Intervals](../guidelines#115-intervals) + - [11.6. Repeating intervals](../guidelines#116-repeating-intervals) + - [**11.7. Evolvable Enums**](#117-evolvable-enums) + - [**11.8. Dictionary Types**](#118-dictionary-types) + - [**11.9. Ommitted Properties**](#119-ommitted-properties) +- [12. Versioning](../guidelines#12-versioning) + - [12.1. Versioning formats](../guidelines#121-versioning-formats) + - [12.1.1. Group versioning](../guidelines#1211-group-versioning) + - [12.2. When to version](../guidelines#122-when-to-version) + - [12.3. Definition of a breaking change](../guidelines#123-definition-of-a-breaking-change) +- [**13. Long running operations**](#13-long-running-operations) + - [**13.1. Resource based long running operations (RELO)**](../long-running-operations#131-resource-based-long-running-operations-relo) + - [**13.2. Stepwise long running operations**](../long-running-operations#132-stepwise-long-running-operations) + - [13.2.1. PUT](../guidelines#1321-put) + - [13.2.2. POST](../guidelines#1322-post) + - [13.2.3. POST, hybrid model](../guidelines#1323-post-hybrid-model) + - [13.2.4. Operations resource](../guidelines#1324-operations-resource) + - [13.2.5. Operation resource](../guidelines#1325-operation-resource) + - [13.2.6. Operation tombstones](../guidelines#1326-operation-tombstones) + - [13.2.7. The typical flow, polling](../guidelines#1327-the-typical-flow-polling) + - [13.2.8. The typical flow, push notifications](../guidelines#1328-the-typical-flow-push-notifications) + - [13.2.9. Retry-After](../guidelines#1329-retry-after) + - [13.3. Retention policy for operation results](../guidelines#133-retention-policy-for-operation-results) +- [14. Throttling, Quotas, and Limits](../guidelines#14-throttling-quotas-and-limits) + - [14.1. Principles](../guidelines#141-principles) + - [14.2. Return Codes (429 vs 503)](../guidelines#142-return-codes-429-vs-503) + - [14.3. Retry-After and RateLimit Headers](../guidelines#143-retry-after-and-ratelimit-headers) + - [14.4. Service Guidance](../guidelines#144-service-guidance) + - [14.4.1. Responsiveness](../guidelines#1441-responsiveness) + - [14.4.2. Rate Limits and Quotas](../guidelines#1442-rate-limits-and-quotas) + - [14.4.3. Overloaded services](../guidelines#1443-overloaded-services) + - [14.4.4. Example Response](../guidelines#1444-example-response) + - [14.5. Caller Guidance](../guidelines#145-caller-guidance) + - [14.6. Handling callers that ignore Retry-After headers](../guidelines#146-handling-callers-that-ignore-retry-after-headers) +- [**15. Push notifications via webhooks**](#15-push-notifications-via-webhooks) + - [15.1. Scope](../guidelines#151-scope) + - [15.2. Principles](../guidelines#152-principles) + - [15.3. Types of subscriptions](../guidelines#153-types-of-subscriptions) + - [15.4. Call sequences](../guidelines#154-call-sequences) + - [15.5. Verifying subscriptions](../guidelines#155-verifying-subscriptions) + - [15.6. Receiving notifications](../guidelines#156-receiving-notifications) + - [15.6.1. Notification payload](../guidelines#1561-notification-payload) + - [15.7. Managing subscriptions programmatically](../guidelines#157-managing-subscriptions-programmatically) + - [15.7.1. Creating subscriptions](../guidelines#1571-creating-subscriptions) + - [15.7.2. Updating subscriptions](../guidelines#1572-updating-subscriptions) + - [15.7.3. Deleting subscriptions](../guidelines#1573-deleting-subscriptions) + - [15.7.4. Enumerating subscriptions](../guidelines#1574-enumerating-subscriptions) + - [15.8. Security](../guidelines#158-security) +- [16. Unsupported requests](../guidelines#16-unsupported-requests) + - [16.1. Essential guidance](../guidelines#161-essential-guidance) + - [16.2. Feature allow list](../guidelines#162-feature-allow-list) + - [16.2.1. Error response](../guidelines#1621-error-response) +- [17. Naming guidelines](../guidelines#17-naming-guidelines) + - [17.1. Approach](../guidelines#171-approach) + - [17.2. Casing](../guidelines#172-casing) + - [17.3. Names to avoid](../guidelines#173-names-to-avoid) + - [17.4. Forming compound names](../guidelines#174-forming-compound-names) + - [17.5. Identity properties](../guidelines#175-identity-properties) + - [17.6. Date and time properties](../guidelines#176-date-and-time-properties) + - [17.7. Name properties](../guidelines#177-name-properties) + - [17.8. Collections and counts](../guidelines#178-collections-and-counts) + - [17.9. Common property names](../guidelines#179-common-property-names) + - [**17.10. Type namespaces**](#1710-type-namespaces) +- [18. Appendix](../guidelines#18-appendix) + - [18.1. Sequence diagram notes](../guidelines#181-sequence-diagram-notes) + - [18.1.1. Push notifications, per user flow](../guidelines#1811-push-notifications-per-user-flow) + - [18.1.2. Push notifications, firehose flow](../guidelines#1812-push-notifications-firehose-flow) + - [**18.2. Additional resources**](#182-additional-resources) + + + +## Summaries of the deltas in the Microsoft Graph Design Guidelines + +### 9a. Resource Design + +#### 9a.1. Noun Resources + +While HTTP defines no constraints on how different resources are related together, it does encourage the use of URL path segment hierarchies to convey a relationship. In addition to the hierarchy of resources, there are also lifetime relationships between resources, the notions of [singletons, entitySets, entities, complex types and navigation properties](entity-complex.md) make it possible to define a set of lifetime relationships between resources. + +#### 9a.2. Verb Resources + +Noun-based resources are not always the best fit for meeting the requirements of a client. There are read-only and write scenarios where a resource can be used to represent some kind of data processing operation. The terms [function and action](Functions-and-actions) are used to identify read and write operation style resources, respectively. + +#### 9a.3. Resource Modeling + +There are a number of principles to be aware of when modeling resources for Microsoft Graph. [modeling variants](modeling-variants) + +### 10. Deltas + +The ability to track changes (pull) occuring in the data exposed by Microsoft Graph. This is addressed implementing [change tracking (aka delta query)](deltas). + +### 11.7. Evolvable Enums + +[Evolvable enums](evolvable-enums) enable the use of enumerations that can add new values over time without breaking clients applications. + +### 11.8. Dictionary Types + +### 11.9. Omitting Properties + +blah-blah [Omitting properties](ommitting-properties) +### 13. Long running operations + +Long running operations are mostly unchanged. The most significant difference is that instead of using `Operation-Location` as the header to point to the Operation, the use of the standard `Location` header is recommended. In hybrid scenarios, a `Content-Location` header can be used to indicate the URL of the created resource and the 202 response can contain a payload. Details of the diffences are described in detail in the [Long Running Operations](long-running-operations) document. + +### 15. Push notifications via webHooks + +To determine how the Graph docs differ than the Microsoft REST API Guidelines. +The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph. This is addressed implementing [change notifications (aka webhooks)](webhooks). + +### 17.10. Type Namespaces + +blah blah [type namespaces](type-namespaces) + +### 18.2. Additional resources The links below provide a set of rules to think through as you design your API. @@ -54,473 +211,3 @@ The links below provide a set of rules to think through as you design your API. - [OData Guidelines](http://www.odata.org/documentation/) - [Microsoft Graph Documentation](https://developer.microsoft.com/en-us/graph/docs/concepts/overview) - [Microsoft Graph Explorer](https://aka.ms/ge) - -::: tip [Need help?] -If you have additional design modeling questions (Identity team only), please ask them on the [Identity API Review Team "General" channel](https://teams.microsoft.com/l/channel/19%3a1013992de7d84c68bce90f7ae69f306a%40thread.skype/General?groupId=11b6f8e9-39e6-41f0-9fdb-dbeda26d4378&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) -::: - -[fdg]: https://www.safaribooksonline.com/library/view/framework-design-guidelines/9780321545671/chapter01.html - - -## Basic Guidance - -Web APIs are already more than 20 years old. The SOAP specification was created in 1998, and the dissertation that became REST was written in 2000. It would take many hours to truly examine the history of APIs, so let's focus on the basics. - -### Resource-oriented architecture - -The first thing we need to discuss is architecture. Leonard Richardson, the author of RESTful Web Services and the more recent RESTful Web APIs, classifies APIs into three architectural categories: resource oriented, RPC oriented, and hybrid (a combination of the two). - -It is critical for us to understand the difference in architectures before we can meaningfully talk about RESTful APIs. The biggest difference is arguably the number of "locations" an operation can be invoked. In RPC architectures, there are comparatively few places to invoke an operation. For instance, SOAP APIs typically expose a single URL, and callers of that API send both an action (the operation to invoke) as well as a request message that contains all of the necessary parameters for invoking the operation. - -REST architectures, on the other hand, have many more "locations" an operation can be invoked. REST focuses on resources, each of which have a unique location. REST uses a URI to represent this location. CRUD operations are typically attached to the resource itself or a parent of the resource. There is often no longer a need for a request message, or if there is, it typically has fewer parameters because the operation is attached to the resource itself. - -We should note right up front that neither architectural style is "evil". They both have pros and cons, and both architectures can meet most needs. Consider the analogy of object-oriented programming versus functional programming. They are both valid styles, and have pros and cons, but neither is "evil". - -### History of REST - -The dissertation that became REST was actually called "Architectural Styles and the Design of Network-based Software Architectures". This dissertation was written by Roy Fielding in 2000, and was based in large part on his experience designing HTTP and URIs. - -If we reflect for a moment on how the Web works, we will better understand RESTful APIs. The Web is, at its most basic essence, a collection of resources – an enormous collection of resources. Web browsers are able to work with all Web sites regardless of whether they were written in pure HTML, Java, Python, or .NET. This all works because of HTTP and URIs. URIs give us a means to uniquely address a resource – a Web page, an image, a stylesheet, or a form submission resource. - -#### Identifying resources - -The unique identifier provided as a URI allows a Web browser to understand and request a particular resource. It also allows resources to be referenced from other resources. This basic concept is known as hypermedia – the linking of resources together using URIs. The URI is useful to many other tools on the Internet, however. The URI is what allows search engines to identify and index resources. In turn, this allows us to search for resources and find the unique identifier that represents that resource. Similarly, proxies are able to cache resources by their identifier either for security, performance, or other purposes. URIs are critical to understanding a resource. - -As a side note, any URI technically constitutes a resource identifier. The URI contains everything from the scheme (HTTP or HTTPS) to the fully qualified domain name to the path to the query string parameters. Furthermore, resources in the purest sense can be a collection of other resources or an individual resource. That said, for Microsoft Graph we have further constrained the definition of "resource" in two ways: - -1. Resources should be individual things, such as a single person or a single task. It is useful to distinguish a collection of individual things as a "resource collection". -2. Query string parameters should not be part of the resource identifier. Most API designs include as much of the resource identification part of the URI as possible in the main URI itself. - -Again, these are Graph preferences and not part of the URI spec. The URI spec uses the broader technical definition. - -#### Using resources - -URIs give us the ability to identify a resource. HTTP gives us the ability to do something with that resource. HTTP has a bunch of built-in methods including `GET` (typically for retrieving a resource), `POST` (often used for creating a resource), and `PATCH` (often used for updating a resource. HTTP has semantics around virtually every aspect of transport from authorization to status to how to construct the request or response headers and body. The HTTP specification is quite large and reasonably mature, so in most cases the guidance in the spec is very clear. - -### Be _openionated_ - -Openionated = opinionated sometimes, open-minded other times - -Given the overall space (public standards, specifications, technology that is used by billions of devices), it is not hard to find people who treat the space as a religion, and publicly shame others who violate (whether from ignorance or on purpose) the specification. REST APIs also have this issue, even though there is only the dissertation and not a public standard for REST. - -> As Microsoft employees, we need to be respectful of others and understand that there are a variety of reasons to do something differently. So first and foremost, engage in dialogue with others and always try to view things optimistically. - -#### Be opinionated when the spec is clear - -That said, there are places to be more opinionated. For instance, parts of the HTTP specification are quite clear and the implications of violating the specification are severe. Let's consider two examples. - -1. The HTTP specification says that `GET` should be both _safe_ and _idempotent_. _Safe_ in this case means that the request does not cause a change in server state, _idempotent_ means that the operation is repeatable many times with the same result. However, some people violate these guidelines and create new resources by sending a `GET` to `{somecollection}/new`. The problem with this design is the billions of devices that understand HTTP. In most cases, those devices understand that the result of a `GET` is cacheable because it is safe and idempotent. Unfortunately, this API design breaks those assumptions and so devices that believe they are working with proper HTTP may in fact be causing significant problems by accessing the API. -2. Similarly, the status codes associated with an HTTP response are not a good forum for creativity. The status codes in the HTTP specification (apart from `418`) all exist for very specific reasons and have benefit to the ecosystem around them. Specification authors do not make their specifications longer than necessary because it's fun to write in spec language. The error codes in the specification are what allow us to write generic clients that understand how to handle a challenge response, or `retry-after`. It is similarly critical for devices to be able to know that if they don't understand error code `474`, they can safely treat it as `400` and know that the client is sending an improper request. - -#### Be open-minded when the spec is unclear - -In other cases the spec is either vague, has evolved, or specifically identifies alternative ways of achieving something. For instance, when the HTTP spec was first created, there was no `PATCH` method. Architects and developers found the need for this method over time, especially with the advent of APIs. The semantic of `PATCH` is that it allows a partial replacement to update a resource. The alternative, `PUT`, requires the full resource to be sent, and the server replaces the resource entirely. In some cases this is inefficient, in other cases it actually doesn't work (for example, if the resource has server-computed values). In any case, the addition of `PATCH` as a different specification gives API developers a choice of how to support update. Guidance is especially necessary when there are multiple conflicting or confusing means of achieving something. (For what it's worth, we recommend always supporting `PATCH` and additionally supporting `PUT` if it makes sense in your scenario.) - - -## Key Principles - -Consistent naming is foundational for API usability. JSON is the leading serialization format for HTTP APIs, so this guidance is designed with JavaScript in mind. - -Property and type names appear in URLs and payloads. - -::: tip REFERENCE -These guidelines draw on the [naming section](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#17-naming-guidelines) of the [Microsoft REST API Design Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md). See also "Naming" in [GMM Level 1](https://msgo.azurewebsites.net/add/design/gmm/gmm-level-1.html). - -The .NET [Framework Design Guidelines](https://www.safaribooksonline.com/library/view/framework-design-guidelines/9780321545671/chapter03.html#ch3) provide rationale for naming guidelines. - -::: - -### General Guidelines - -::: tip ✔ DO use `lowerCamelCase` for _all_ names. - -- Right: `automaticRepliesStatus`. -- Wrong: `kebab-case` or `snake_case`. - -::: - -::: warning ✖ AVOID redundant words in names. - -- Right: `/places/{id}/`**_type_** and `/phones/{id}/`**_number_** -- Wrong: `/places/{id}/`_**placeType**_ and `/phones/{id}/`**_phoneNumber_** - -::: - -::: warning ✖ AVOID using brand names in type or property names. - -- Right: `chat` -- Wrong: `teamsChat` - -::: - -::: warning ✖ AVOID using acronyms or abbreviations unless they are broadly understood. - -- Right: `url` or `htmlSignature` -- Wrong: `msodsUrl` or `dlp` - -::: - -::: tip ✔ DO use singular nouns for type names. - -- Right: `address` -- Wrong: `addresses` - -::: - -::: tip ✔ DO use plural nouns for collections (for listing a type or collection properties). - -- Right: `addresses` -- Wrong: `address` - -::: - -::: tip ✔ DO pluralize the noun even when followed by an adjective (a "postpositive"). - -- Right: `passersby` or `mothersInLaw` -- Wrong: `notaryPublics` or `motherInLaws` - -::: - -### Casing - -::: tip ✔ DO case two-letter acronyms with the same case. - -- Right: `ioLimit` or `totalIOAmount` -- Wrong: `iOLimit` or `totalIoAmount` - -::: - -::: tip ✔ DO case three+ letter acronyms the same as a normal word. - -- Right: `fidoKey` or `oauthUrl` -- Wrong: `webHTML` - -::: - -::: danger ✖ DO NOT capitalize the word following a prefix or words within a compound word. - -- Right: `subcategory`, `geocoordinate` or `crosswalk` -- Wrong: `metaData`, `semiCircle` or `airPlane` - -::: - -::: tip ✔ DO capitalize within hyphenated and open (spaced) compound words. - -- Right: `fiveYearOld`, `daughterInLaw` or `postOffice` -- Wrong: `paperclip`, `changingroom` or `fullmoon` - -::: - -### Prefixes and Suffixes - -::: tip ✔ DO suffix date and time properties. - -- Right: `dueDate`—an `Edm.Date` -- Right: `createdDateTime`—an `Edm.DateTimeOffset` -- Right: `recurringMeetingTime`—an `Edm.TimeOfDay` -- Wrong: `dueOn` or `startTime`, both an `Edm.DateTimeOffset` - -::: - -::: danger ✖ DO NOT suffix property names with primitive type names unless the type is temporal. - -- Right: `isEnabled` or `amount` -- Wrong: `enabledBool` - -::: - -::: tip ✔ DO prefix property names for properties concerning a different entity. - -- Right: `siteWebUrl` on `driveItem`, or `userId` on `auditActor` -- Wrong: `webUrl` on `contact` when its the `companyWebUrl` - -::: - -### Common property names - -| Approved name | Type | Use | -| ---------------------- | -------------- | --------------------------------------------------------- | -| `displayName` | String | A label that can be displayed or read aloud. Not `name`. | -| `webUrl` | String | The web page for viewing or editing this entity. | -| `url` | String | A URL to a resource. (In Graph often holds the `webUrl`.) | -| `lastModifiedDateTime` | DateTimeOffset | The last time this entity changed. | -| `createdDateTime` | DateTimeOffset | The time this entity was created. | -| `createdBy` | identitySet | The creator of this entity. | -| `createdByUser` | user | The user in `/users` who created this entity. | - - - -## Modelling Patterns - -### Adding new subtypes - -Table of Contents - -- [Overview](#overview) -- [Background](#background) -- [Risks](#risks) -- [Mitigation](#mitigation) -- [Shielding clients](#shielding-clients) - -This article discusses the consequences of introducing a new sub-type in the Microsoft Graph schema for a type that are used in collections. - -A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes (see [modeling variants](modeling-variants.md)). This allows to model collections of objects that have slightly different behavior. The common behavior is represented in the base type and the variations in a subtype, a concept very familiar from OO programming languages. It is straightforward to add a new subtype to the hierarchy with some consequences to the backwards compatibility as shown below. - -#### Overview - -OData allows to design collections of entities (entity sets, multi valued navigation properties) with values of different types. Currently these different types have to be subtypes of a common base type (often an abstract type). In the current version of Microsoft Graph are many collections of items that represent slightly different things, variants of one concept. For example, the [managedAppPolicy](https://docs.microsoft.com/en-us/graph/api/resources/intune-mam-managedapppolicy?view=graph-rest-1.0) type represents a base type for a variety of platform specific policies including for example a windowsInformationProtection policy. And when sending a GET request to the URL `/deviceAppManagement/managedAppPolicies` a collection of a mix of the sub-types is returned. -Most prominent are the collections of type directoryObject, an abstract base entity type that is implemented by types like user, groups, devices, etc.. - -Even though OData has means to express these subtypes and adding new subtypes is syntactically a backwards compatible change, there are situations that impose some risk to break client applications. - -This article discusses the steps to ensure backwards compatibility of adding new subtypes that are potentially returned in collections. - -#### Background - -For heterogeneous collections, OData ensures that the client is able to distinguish the different types of the element of an collections. So for example querying - -```HTTP -GET https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners -``` - -returns a collection where each element has an additional property `@odata.type` - -```JSON -{ - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#directoryObjects", - "value": [ - { - "@odata.type": "#microsoft.graph.user", - "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", - "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" - // ... - } - ] -} -``` - -Using the `@odata.type` property, the client code can decide how to deserialize the code, for example deciding the class used to create an object/instance. - -Clients must anticipate that new subtypes get introduced and write code to guard against these situations. If not handled appropriately, the existing types most likely don't have the properties to store the returned JSON properties and these values have to be dropped. At the same time properties of the existing types can't get a value assigned. But even in untyped client code, without some compensation, there is no code that "looks" at the unexpected properties and misses the expected ones. - -How to guard against and handle these situations is very specific to the client application and requires to understand the intended semantics of the types returned by the service. - -#### Risks - -There are a few potential risks when new sub-types are introduced. They are all variants of a) the fact that syntactically, previously expected properties might be absent and the received properties are ignored and b) the semantic role that the existing and new subtype play are potentially changing. - -- De-serialization code might break because of missing properties in returned collection items. Even though property X was mandatory on all subtypes previously returned, the new subtype might not have this property and the client code needs to deal with that. - -- Client libraries for strongly typed language might ignore some of the values in the @odata.type property without further configuration and need to be configured to be able to pick the right (client) type to deserialize into. - -- In the client code it is reasonable to assume, based on existing running code, the "world" is exclusively described by the current subtypes. E.g. there is an understanding in Microsoft Graph's directory workload that there are two types of actors: users and servicePrincipals. Introducing a new subtype that can be an actor in directory (e.g. a device) would require a lot of clients to change to anticipate the presence of an object of that type and deal with it's own set of properties. In essence: originally each object was one of n types. After the change, an object can be neither of the n (because it is of the n+1's type). - -#### Mitigation - -Following are some of the techniques to mitigate these situation. - -##### Avoid overgeneralized base types - -If the abstract base type has many subtypes, it is quite likely that specific collection only ever contains a few subtypes. Since the type hierarchy is wide, there is probably some functionality or behavior that is only shared amongst a few but not all subtypes. That is ultimately explaining why some collection only contains some of the subtypes, the ones that share some behavior. - -A well-known example for this situation is the `directoryObject` type which has many sub-types and only one property, `id`. Collections like for example the `owners` property on a `group` is declared as `directoryObject` and in reality only `user`s and `servicePrincipal`s are added to this collection since these are informally the only actors modeled in directory. - -If one only focuses on the hierarchy, one could easily think it is straightforward to add a new subtype. What is necessary is the ensure that adding a new subtype doesn't change the semantic of the type hierarchy and the semantic of the property with it's implicit constraints. - -##### Roll-out sequence - -Microsoft Graph does not return object from a workload that has a type that is not configured in current metadata. That leads to behaviors that is slightly different depending if the object is returned as part of a collection or is requested individually. - -If an object of an un-configured type is returned by the workload as part of a collection, the object just gets excluded from the collection and not returned. Microsoft Graph just doesn't know yet how to serialize the object. - -If an object of an un-configured type is requested directly via an entity set, for example in case of `directoryObject` and a request like `/v1.0/directoryObject/{guid}`, Microsoft Graph returns an empty method body (not a 404 Not Found). It is assumed that the object and it's Id can not be found anywhere in the system and it is safe to ignore it. And Microsoft Graph just doesn't know yet how to serialize the object. - -To make sure that the users do not get exposed to the second behavior, ensure that newly introduced entities are first known by graph before they get added to the collections. -The configuration allows Microsoft Graph to respond with the details/properties of the entities. Without that configuration Microsoft Graph returns no response body. -This leads necessarily to a two-step process of first introducing the entity type but not return them in any of the heterogeneous collections. And only after that returning them as items of collections. This can of be done in relatively rapid succession. - -This is often not a problem since for utterly new entity types, no collection every has items of that type. But if the workload has APIs beside Microsoft Graph, these entities might have been added to the collections through that API. - -##### Allow time for testing - -Inform the clients about the change and allow them to test the changes in beta. Time is required implement the code necessary to deal with the new entity type, both in terms of de-serialization as well as integrating it into the rest of the application. - -##### Communicate the change in semantics - -Even more importantly, it is necessary for the client developers to incorporate the new semantic into their application/service, even if the change is perceived small. The addition of new data needs design changes in the client application. These changes potentially ripple through many layers of that application/service. This requires early communication and clear documentation what the new type represents and why/how it is considered a subtype of the original abstract type of the collection. Without that information the client application will not be able to process that data returned in the responses. - -For example lets assume a situation where owners of a group are people and the only type ever returned as a member of the `owners` property of the `group` entity type is of type `user`. It is reasonable to assume that certain behaviors/functionality exists for these members of the owners collection. For example, every owner has an email address, every owner has a manager. By introducing new types of items to this collection, these assumption might not be true anymore (e.g. owners can be machine accounts without a manager or email). Even though the protocol and client libraries have ways to deal with the transport and de-serialization of these new types, it requires some new design how downstream modules of the client applications/services deal with entities that don't have email addresses or managers. - -#### Shielding Clients - -[TODO: describe upcoming features in Microsoft Graph to ensure full backwards compatibility] - - -### Entity Types and Complex Types - -The [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html), beside many other things, provides a way to describe the structure of the requests and responses of an OData service via the [Common Schema Definition Language](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html) (CSDL). - -CSDL defines a few ways to define [types](https://en.wikipedia.org/wiki/Data_type) and the most prominent are Entity Types and Complex Types. These two have some similarities and some differences that we are going to explore in this article. - -#### Entity Types - -Entity Types are that most common way to define the structure of the requests and responses of an OData service. The standard [defines](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_EntityType): - -> Entity Types are nominal structured types with a key that consists of one or more references to structural properties. An entity type is the template for an entity: any uniquely identifiable record such as a customer or order.. - -There is three things to note in that (arguably terse) definition: - -- By "structured" the standard means that an Entity Type is defined by enumerating its (typed) **properties**. -- "nominal" just refers to the fact that the type **has a name** (a name in the CSDL schema). -- And the most important piece in the context of this article is that an Entity Type declares a **key property** and the consequence is that objects of this type can be uniquely identified through this key. In Microsoft Graph that key is currently always the property named "id". The standards allows more variation and Microsoft Graph might also relax this constraint in the future. - -How they key is used to identify an object is a bit out of the scope of this document. For now it should suffice to say, that it is used as part of the URL to "name" an individual object. For Example in the URL https://localhost/api/authors/50 (or https://localhost/api/authors(50) ), the 50 is the key of an object. - -#### Complex Types - -Complex Types are non-scalar properties of entity types that enable scalar properties to be organized within entities. Complex types consist of a list of properties with no key, and can therefore only exist as properties of a containing entity. You can use complex types to group fields together without exposing them as an independent OData entity. Complex types can contain complex types, that is, they can be deeply nested. - -- A complex type doesn't have keys and therefore cannot exist independently. -- Complex type can only exist as properties of entity types or other complex types. -- It cannot participate in relationships (see navigation properties) directly. - -#### Comparison - -In the example below, we have added an Author as an Entity Type and Address a Complex Type. - -```XML - - - - - - - - - - - - - - -``` - -You can see that Address type does not have any sort of key property. Complex types cannot be tracked on their own, so as a property in the Author class, it will be **tracked as part of** an author object. The consequence is that its lifecycle is coupled to the enclosing Entity Type: When the author gets deleted, the address gets deleted as well. - -#### Summary - -In Summary: - -- Both Entity Types and Complex types are named types that declare a list of properties for the objects of that type. -- An Entity Type always has a key declared whereas a Complex type doesn't. -- Objects of an Entity Type can be directly addressed via an URL but ComplexTypes are always contained in an EntityType object and can only be addressed through a combination of an Entity address and a property name. (see more at [Navigation Properties and Containment](navigation-containment.md)) - -### Adding Members to Enumerations - -Microsoft Graph services sometimes want to add a member to an enumeration type. However, there are barriers. First and foremost, some deserializers (including Json.NET) fail if an enumeration property has a value not found in that property's enumeration type. Second, a client may not deal with enumeration values unknown to it. The `Evolvable Enumerations` pattern and implementation allows a member to be safely be added to an enumeration. - -#### Evolvable Enumerations - -An evolvable enumeration contains the sentinel member `unknownFutureValue` after which new enumeration members are added. Consider the following enumeration: - -```xml - - - - ... - - - -``` - -From this the C# SDK generates: - -```csharp -public enum weekday -{ - monday, - tuesday, - ... - sunday, - unknownFutureValue -} -``` - -The new enumeration member `newday` is added after `unknownFutureValue`: - -```xml - - - - ... - - - - -``` - -From this the C# SDK generates: - -```csharp -public enum weekday -{ - monday, - tuesday, - ... - sunday, - unknownFutureValue, - newday // new value -} -``` - -#### Methods and client opt-in - -On POST, if any enumeration property of the entity contains `unknownFutureValue`, the request will fail with `400 Bad Request`. On PATCH, any enumeration property with value `unknownFutureValue` is ignored--that property is not updated. - -Callers signal their ability to process added members by including the `include-unknown-enum-members` preference: - -```http -GET /me/calendar -Prefer: include-unknown-enum-members -``` - -Upon GET, when this header is absent, `unknownFutureValue` is returned to the caller for enumeration property values that are one of the added enumeration members. When this header is present, the enumeration value is returned unchanged. - -Upon a filtered GET, when this header is absent, if `unknownFutureValue` appears in a `$filter` clause it matches any added enumeration member. For example, if the enumeration members `newday` and `anotherNewDay` have been added to `weekday`, these are equivalent: - -```http -$filter=weekday eq unknownFutureValue -$filter=weekday eq newday or weekday eq anotherNewDay -$filter=weekday ge unknownFutureValue -``` - -If the header is absent, `$filter=weekday` **eq** `unknownFutureValue` matches any new enumeration value. If the header is present, that same filter matches nothing, while `$filter=weekday` **ge** `unknownFutureValue` matches any new enumeration value. (Note: the latter matches new enumeration values whether the header is present or not.) - -#### SDK code generation - -The SDK generates enumeration definitions from the current schema. Requests always include the `include-unknown-enum-members` header. - -At runtime, since other members could have been added to the enumeration after code was generated, values unknown to the generated code are translated to `unknownFutureValue`. When the service sees an enumeration property with the value `unknownFutureValue`, it will ignore it and not update the property. - -#### Resetting an Evolvable Enumeration - -Upon a major version change, `unknownFutureValue` can be moved to the end of the enumeration, making known the previously unknown enumeration members. - -```csharp -public enum weekday -{ - monday, - ... - sunday, - newday, - anotherNewDay, - unknownFutureValue -} -``` - - -## Common Patterns - -## Advanced Patterns - -## Design Rules \ No newline at end of file diff --git a/graph/advanced-patterns/long-running-operations.md b/graph/long-running-operations.md similarity index 89% rename from graph/advanced-patterns/long-running-operations.md rename to graph/long-running-operations.md index 2bb9e5b4..d52c6e51 100644 --- a/graph/advanced-patterns/long-running-operations.md +++ b/graph/long-running-operations.md @@ -1,7 +1,4 @@ ---- -title: Long-running operations -owner: mastaffo ---- +[[_TOC_]] # Long-running operations @@ -10,7 +7,7 @@ Long running operations, sometimes called async operations, tend to mean differe 1. One or more clients MUST be able to monitor and operate on the same resource at the same time. 2. The state of the system SHOULD be discoverable and testable at all times. Clients SHOULD be able to determine the system state even if the operation tracking resource is no longer active. The act of querying the state of a long running operation should itself leverage principles of the web. i.e. well defined resources with uniform interface semantics. Clients MAY issue a GET on some resource to determine the state of a long running operation 3. Long running operations SHOULD work for clients looking to "Fire and Forget" and for clients looking to actively monitor and act upon results. -4. Cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a cancelled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. +4. Cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a canceled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. ## Resource based long running operations (RELO) @@ -122,9 +119,8 @@ Services that provide the `/operations` resource MUST provide GET semantics. GET | Running Operations | Operation Creation Time | | Completed Operations | Operation Creation Time | -::: tip -**Note:** that "Completed Operations" is a goal state (see below), and may actually be any of several different states such as "successful", "cancelled", "failed" and so forth. -::: +> TIP
+> **Note:** that "Completed Operations" is a goal state (see below), and may actually be any of several different states such as "successful", "canceled", "failed" and so forth. ### Operation resource @@ -134,9 +130,8 @@ An operation is a user addressable resource that tracks a stepwise long running Services MAY support operation cancellation by exposing DELETE on the operation. If supported DELETE operations MUST be idempotent. -::: tip -**Note:** From an API design perspective, cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a cancelled operation It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. -::: +> TIP
+> **Note:** From an API design perspective, cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a canceled operation It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. Services that do not support operation cancellation MUST return a 405 Method Not Allowed in the event of a DELETE. @@ -147,9 +142,9 @@ Operations MUST support the following states: 3. Succeeded. Terminal State. 4. Failed. Terminal State. -Services MAY add additional states, such as "Cancelled" or "Partially Completed". Services that support cancellation MUST sufficiently describe their cancellation such that the state of the system can be accurately determined and any compensating actions may be run. +Services MAY add additional states, such as "Canceled" or "Partially Completed". Services that support cancellation MUST sufficiently describe their cancellation such that the state of the system can be accurately determined and any compensating actions may be run. -Services that support additional states should consider this list of canonical names and avoid creating new names if possible: Cancelling, Cancelled, Aborting, Aborted, Tombstone, Deleting, Deleted. +Services that support additional states should consider this list of canonical names and avoid creating new names if possible: Canceling, Canceled, Aborting, Aborted, Tombstone, Deleting, Deleted. An operation MUST contain, and provide in the GET response, the following information: @@ -321,9 +316,8 @@ Location: http://api.contoso.com/v1.0/operations/123 Retry-After: 60 ``` -::: tip -**Note:** The use of the HTTP Date is inconsistent with the use of ISO 8601 Date Format used throughout this document, but is explicitly defined by the HTTP standard in [RFC 7231][rfc-7231-7-1-1-1]. Services SHOULD prefer the integer number of seconds (in decimal) format over the HTTP date format. -::: +> TIP
+> **Note:** The use of the HTTP Date is inconsistent with the use of ISO 8601 Date Format used throughout this document, but is explicitly defined by the HTTP standard in [RFC 7231][rfc-7231-7-1-1-1]. Services SHOULD prefer the integer number of seconds (in decimal) format over the HTTP date format. ## Retention policy for operation results diff --git a/graph/modeling-patterns/README.md b/graph/modeling-patterns/README.md deleted file mode 100644 index 7829cf3a..00000000 --- a/graph/modeling-patterns/README.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Modeling Patterns -owner: mastaffo ---- - -# Coming soon diff --git a/graph/modeling-patterns/shared-types.md b/graph/modeling-patterns/shared-types.md deleted file mode 100644 index 0eadf0f5..00000000 --- a/graph/modeling-patterns/shared-types.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Shared types -owner: mastaffo ---- - -# Working with shared types aka referencing existing models from your schema - -You will frequently need to connect your models to other entities on the Graph (indeed, this is a key value of the Microsoft Graph). -To do this, you will have to provide reference versions of the entities you need in your own schema. - -For each entity you need to refer to: - -- Add a declaration to your own schema. This will live under your own namespace, so refer to it as though it was in your own namespace -- Omit the `ags:IsMaster` annotation to indicate that your service does not master this entity -- Do not express inheritance relationships between external entities -- Do not include a copy of the 'virtual' entity named `entity` - this is synthesized by Microsoft Graph itself - -Duplicate enumerations and complex types - these must be expressed (and identical) in every workload that shares them. diff --git a/graph/modeling-patterns/modeling-variants.md b/graph/modeling-variants.md similarity index 94% rename from graph/modeling-patterns/modeling-variants.md rename to graph/modeling-variants.md index 0f2d97fb..76ffebee 100644 --- a/graph/modeling-patterns/modeling-variants.md +++ b/graph/modeling-variants.md @@ -1,4 +1,4 @@ ---- +--- title: Modeling variants owner: chrispre --- @@ -13,7 +13,7 @@ Frequently we encounter situations where a certain piece of data in Microsoft Gr All these variants have different properties representing the information needed in these cases. -OData and Microsoft Graph offer different ways to model the API and these different variants. We'll describe those here, and list the advantages and disadvantages of each modelling technique. +OData and Microsoft Graph offer different ways to model the API and these different variants. We'll describe those here, and list the advantages and disadvantages of each modeling technique. In the remainder of the document we are using the term "variant" instead of "kind", "flavor", "type". "type" is defined by OData and we do not want to presume there has to be a type per variant. @@ -48,13 +48,13 @@ The key here is that for each of these values, some properties are meaningful an Below are a few pros and cons to decide which pattern to use. - In **[hierarchy](#type-hierarchy)**, the interdependencies of properties, i.e. which properties are relevant for which variants, is fully captured in metadata and client code can potentially leverage that to construct and/or validate requests. -- Introducing new cases in **[hierarchy](#type-hierarchy)** is relatively isolated (which is why it is so familiar to OOP) and is considered backwards compatible (at least syntactically). But see the note about [changing sematics](#semantics) below. +- Introducing new cases in **[hierarchy](#type-hierarchy)** is relatively isolated (which is why it is so familiar to OOP) and is considered backwards compatible (at least syntactically). But see the note about [changing semantics](#semantics) below. - Introducing new cases/variants in **[facets](#facets)** is straightforward. One needs to be careful since it can introduce situations where previously exactly one of the facets was non-null and now all the old ones are null. For example imagine a new facet "shortcut" is added to the example above where everything was one of folder,file,image,photo. Adding the shortcut facet means that there are now object with all of the previous four are null. This is not unlike adding new subtypes in the hierarchy pattern or adding a new type value in the flat pattern. - **[hierarchy](#type-hierarchy)** and **[facets](#facets)** (to a slightly lesser degree) are well suited for strongly typed client programming languages. Whereas **[flat](#flat)** is more familiar to developers of less strongly typed languages. - **[facets](#facets)** has the potential to model what is typically associated with multiple inheritance (but it is not inheritance so please don’t quote me). Just to illustrate the point and constructing a highly hypothetical scenario, in the OneDrive example, having an item be a folder and a photo is easy to represent. - **[facets](#facets)** and **[flat](#flat)** lend to syntactically simpler filter query expression. **[hierarchy](#type-hierarchy)** is more explicit but requires the less well known cast segments in the filter query. For example, if one wants to filter on the importance of a mail in a collection of outlookItems, one first needs to "cast" to mailItem to then filter on the importance property: `$filter=microsoft.graph.mailItem/importance eq 'High'`. -- **[flat](#flat)** might resemble a structure that that developers are familiar with from on-prem products and their API (e.g. recurrence in Microsoft Graph is modeled after Exchange Server's model). Even though the Graph API can and should abstract from the implementation details this can have benefits in documentation and adoption. +- **[flat](#flat)** might resemble a structure that developers are familiar with from on-prem products and their API (e.g. recurrence in Microsoft Graph is modeled after Exchange Server's model). Even though the Graph API can and should abstract from the implementation details this can have benefits in documentation and adoption. - **[hierarchy](#type-hierarchy)** can become hard to maintain if the base type is quite abstract and the hierarchy is relatively wide. Lets assume a situation where collections are modeled using the base type with many sub-types, but the actual elements of the collection are only ever one or two of the sub-types. When a new subtype gets introduced and the collection(s) quickly contain elements of this new sub-type, client code has to react to these changes. It is important to check if this changes the semantics of the property (actual or assumed). See also [changing semantics](#semantics) below. - Even though not frequently used in Microsoft Graph, **[hierarchy](#type-hierarchy)** can be refined by annotating the collections with OData `derived type constraints` (see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Validation.V1.md)). This annotation restricts the values to certain sub-trees of an inheritance hierarchy. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return object of a type that is semantically not suitable. diff --git a/graph/naming-conventions.md b/graph/naming-conventions.md new file mode 100644 index 00000000..8a04f3cb --- /dev/null +++ b/graph/naming-conventions.md @@ -0,0 +1,119 @@ + +### General Guidelines + +::: tip ✔ DO use `lowerCamelCase` for _all_ names. + +- Right: `automaticRepliesStatus`. +- Wrong: `kebab-case` or `snake_case`. + +::: + +::: warning ✖ AVOID redundant words in names. + +- Right: `/places/{id}/`**_type_** and `/phones/{id}/`**_number_** +- Wrong: `/places/{id}/`_**placeType**_ and `/phones/{id}/`**_phoneNumber_** + +::: + +::: warning ✖ AVOID using brand names in type or property names. + +- Right: `chat` +- Wrong: `teamsChat` + +::: + +::: warning ✖ AVOID using acronyms or abbreviations unless they are broadly understood. + +- Right: `url` or `htmlSignature` +- Wrong: `msodsUrl` or `dlp` + +::: + +::: tip ✔ DO use singular nouns for type names. + +- Right: `address` +- Wrong: `addresses` + +::: + +::: tip ✔ DO use plural nouns for collections (for listing a type or collection properties). + +- Right: `addresses` +- Wrong: `address` + +::: + +::: tip ✔ DO pluralize the noun even when followed by an adjective (a "postpositive"). + +- Right: `passersby` or `mothersInLaw` +- Wrong: `notaryPublics` or `motherInLaws` + +::: + +### Casing + +::: tip ✔ DO case two-letter acronyms with the same case. + +- Right: `ioLimit` or `totalIOAmount` +- Wrong: `iOLimit` or `totalIoAmount` + +::: + +::: tip ✔ DO case three+ letter acronyms the same as a normal word. + +- Right: `fidoKey` or `oauthUrl` +- Wrong: `webHTML` + +::: + +::: danger ✖ DO NOT capitalize the word following a prefix or words within a compound word. + +- Right: `subcategory`, `geocoordinate` or `crosswalk` +- Wrong: `metaData`, `semiCircle` or `airPlane` + +::: + +::: tip ✔ DO capitalize within hyphenated and open (spaced) compound words. + +- Right: `fiveYearOld`, `daughterInLaw` or `postOffice` +- Wrong: `paperclip`, `changingroom` or `fullmoon` + +::: + +### Prefixes and Suffixes + +::: tip ✔ DO suffix date and time properties. + +- Right: `dueDate`—an `Edm.Date` +- Right: `createdDateTime`—an `Edm.DateTimeOffset` +- Right: `recurringMeetingTime`—an `Edm.TimeOfDay` +- Wrong: `dueOn` or `startTime`, both an `Edm.DateTimeOffset` + +::: + +::: danger ✖ DO NOT suffix property names with primitive type names unless the type is temporal. + +- Right: `isEnabled` or `amount` +- Wrong: `enabledBool` + +::: + +::: tip ✔ DO prefix property names for properties concerning a different entity. + +- Right: `siteWebUrl` on `driveItem`, or `userId` on `auditActor` +- Wrong: `webUrl` on `contact` when its the `companyWebUrl` + +::: + +### Common property names + +| Approved name | Type | Use | +| ---------------------- | -------------- | --------------------------------------------------------- | +| `displayName` | String | A label that can be displayed or read aloud. Not `name`. | +| `webUrl` | String | The web page for viewing or editing this entity. | +| `url` | String | A URL to a resource. (In Graph often holds the `webUrl`.) | +| `lastModifiedDateTime` | DateTimeOffset | The last time this entity changed. | +| `createdDateTime` | DateTimeOffset | The time this entity was created. | +| `createdBy` | identitySet | The creator of this entity. | +| `createdByUser` | user | The user in `/users` who created this entity. | + diff --git a/graph/modeling-patterns/navigation-containment.md b/graph/navigation-containment.md similarity index 100% rename from graph/modeling-patterns/navigation-containment.md rename to graph/navigation-containment.md diff --git a/graph/modeling-patterns/odata-types-primer.md b/graph/odata-types-primer.md similarity index 100% rename from graph/modeling-patterns/odata-types-primer.md rename to graph/odata-types-primer.md diff --git a/graph/advanced-patterns/omitting-properties.md b/graph/omitting-properties.md similarity index 75% rename from graph/advanced-patterns/omitting-properties.md rename to graph/omitting-properties.md index e5b7bc8b..018e78f5 100644 --- a/graph/advanced-patterns/omitting-properties.md +++ b/graph/omitting-properties.md @@ -1,18 +1,14 @@ ---- -title: "RFC: Omitting properties" -owner: piotrci ---- +[[_TOC_]] # RFC: Omitting properties -::: danger Review period ends 10/10/2018 - -::: +> Warning
Review period ends 10/10/2018
+> + There are scenarios where the server contains business logic that determines if a property value should be returned, or not, to the client. Even when the client explicitly requests the property, it may be purposefully omitted from the response by the server. This section provides guidance on how the server should explicitly represent omitted properties in the response. -::: tip -In such scenarios, the property is publicly known and published through the schema. Clients are aware of the property and they can request it, but the server decides to not return it. -::: +> TIP
+> In such scenarios, the property is publicly known and published through the schema. Clients are aware of the property and they can request it, but the server decides to not return it. ## Scenarios @@ -24,7 +20,7 @@ These are scenarios existing today where omitting properties is desirable: ## `omitted` annotation -Returing `null` values or implicitly hiding properties creates ambiguity and does not promote correct app logic. Instead, when the server decides to omit property values, it should explictly state that properties are omitted in the response. +Returning `null` values or implicitly hiding properties creates ambiguity and does not promote correct app logic. Instead, when the server decides to omit property values, it should explicitly state that properties are omitted in the response. When a property is omitted, it should still be returned with a `null` value; this allows client code that expects the property to work seamlessly and makes the handling of the annotations optional. @@ -38,7 +34,7 @@ In addition to the null property, a corresponding annotation should be included ``` - `propertyName` matches the name of the original property. It is followed by the annotation `@omitted`. -- The value is a JSON payload with one property - `code` - whose value is one of the pre-defined "reason codes". This value can be interpreted programatically, and it is also human-readable. +- The value is a JSON payload with one property - `code` - whose value is one of the predefined "reason codes". This value can be interpreted programatically, and it is also human-readable. App code can interpret this portion of the response to pivot its business logic. For example, a developer can create a multi-tenant application that works in all tenants, even ones that never have access to certain property values; the app can detect when values are omitted and react as appropriate. @@ -85,15 +81,14 @@ Code values should be well defined and documented. There should be a unique code These are the code values based on existing scenarios exposed through Graph today: -| Code | Scenario | -| ----------------------- | -------------------------------------------------------------------------------------------------------------------- | -| licensedProductRequired | A licensed product is required in the tenant, or a licene must be assigned to the user whose data is being accessed. | -| limitedPermissions | The app or user permissions used to make the call are insufficient to access the specific property. | -| limitedRole | The role of the app or user in relation to the target entity is insufficient to access the specific property. | +| Code | Scenario | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------- | +| licensedProductRequired | A licensed product is required in the tenant, or a license must be assigned to the user whose data is being accessed. | +| limitedPermissions | The app or user permissions used to make the call are insufficient to access the specific property. | +| limitedRole | The role of the app or user in relation to the target entity is insufficient to access the specific property. | -::: tip -The difference between `limitedPermissions` and `limitedRole` is subtle, but important. The former will result in an omitted property for all entities in a collection. For example, using the `User.ReadBasic.All` permission will omit properties from **all** users returned. The latter may result in properties omitted for only some entities. For example, a teacher reading users in a school may see full properties for students in their classroom, while seeing limited properties for the rest of the students. -::: +> TIP
+> The difference between `limitedPermissions` and `limitedRole` is subtle, but important. The former will result in an omitted property for all entities in a collection. For example, using the `User.ReadBasic.All` permission will omit properties from **all** users returned. The latter may result in properties omitted for only some entities. For example, a teacher reading users in a school may see full properties for students in their classroom, while seeing limited properties for the rest of the students. ### When to return annotations @@ -104,7 +99,7 @@ The difference between `limitedPermissions` and `limitedRole` is subtle, but imp ## Callers opt-in to this behavior -Unless the caller explicitly opts-in into omit annotations, the response should simply ignore the property and not include it in the reponse. Only when the specific header value is included in the request, should the behavior described above kick in. +Unless the caller explicitly opts-in into omit annotations, the response should simply ignore the property and not include it in the response. Only when the specific header value is included in the request, should the behavior described above kick in. The header used to opt-in is as follows: diff --git a/graph/shared-types.md b/graph/shared-types.md new file mode 100644 index 00000000..0d9bdd03 --- /dev/null +++ b/graph/shared-types.md @@ -0,0 +1,212 @@ +[[_TOC_]] + +# Overview + +This topic describes three shared types scenarios: + +1. Shared type object reuse (enums and complex types) +2. Extending existing resources (entity types) that another workload masters +3. Shared entity types - where different workloads want to share a common schema + +For each scenario we'll show you how to update your workload schema (with the requisite AGS annotated schema), with some examples. + +## Shared object reuse (enums and complex types) + +We encourage API owners to reuse types where possible - some examples of some common shared types being `keyValuePair` and `patternedRecurrence`. + +You can reuse any existing enum or complex type in your workload CSDL by declaring the full type definition in an identical manner to anywhere else it is declared. Any difference in the type's public declaration across workload CSDLs that define that same type will lead to (GMM) rule validation errors. NOTE: this does not include +workload specific annotations (such as ags:WorkloadName). If you want to change anything about these types (like change a complex type to be an open type or making a property nullable), you'll need to make that change in **all** workload CSDLs that declare that type, and then get sign-off from those workload owners. + +## Referencing and extending existing schema (entity types) + +You will frequently need to connect your models/APIs to other entities in Microsoft Graph. This is a key value of Microsoft Graph. For example, you might have some data or properties that you want to add and surface on another entity type that is mastered by another workload. An example of this might be to add a `signInActivity` (complex type) property to the existing `user` entity type. The data for `signInActivity` comes from **workload A**, while `user` is mastered by **DirectoryServices workload**. This "extending existing schema" is sometimes also referred to as a "composite type" where querying the entity returns data from multiple workloads (although we strive to hide that fact from the Microsoft Graph caller). + +> **NOTE**: You cannot extend [shared entity types](#shared-entity-types). + +### How to + +To enable this, you will have to provide reference versions of the entities you need in your own workload schema. + +- Add a declaration to your own workload schema for the entity type(s) you want to extend. This will live under your own namespace, so refer to it as though it was in your own namespace. +- Omit the `ags:IsMaster` annotation to indicate that your service does not master this entity type +- Do not express inheritance relationships between external entities +- Do not include a copy of the 'virtual' entity named `entity` - this is synthesized by Microsoft Graph itself +- Add your `ags:AddressUrl` for the endpoint that implements the entity type in your service. It should be added to an "entry point" in your schema. In this case, that's either on a singleton or entity set. In general, other entry points could be navigation properties, functions or actions. +- Extend the entity type with the set of properties that your service masters. These can be primitive type properties, complex type properties, navigation properties or even binding functions and actions. + +### Examples + +Here's an example for `signInActivity` taken from the Microsoft.AAD.Reporting.csdl workload, that adds a property to the `user` entity type. This property will show up in the user entity type definition in the [Microsoft Graph CSDL](https://graph.microsoft.com/beta/$metadata). For the sake of brevity the complex type definition for `signInActivity` is not shown: + +```xml + + + + + + + + +``` + +Here's another (abbreviated) example from the Microsoft.Exchange.csdl workload where a `messages` contained navigation property is added to the `user` entity type, to enable paths such as `https://graph.microsoft.com/v1.0/users/{id}/messages`. + +```xml + + + + + + +``` + +### Paging and filtering + +When extending existing entity types (with standard properties), for those properties to be returned, the caller **must** use `$select` (i.e. they do not return by default). The Microsoft Graph front-end (AGS) will send requests to all endpoints that contribute to the "composite type" and merge the result. + +To enable **paging** (and filtering), your service will need to implement **bulk fetch operation** support, typically in the form of an action that gets records by IDs, similar to what [Directory Services offers](https://docs.microsoft.com/graph/api/directoryobject-getbyids?view=graph-rest-1.0&tabs=http). The request body that AGS sends will contain an "ids" array property. This does not need to be a public action and can remain internal. Once you have this bulk fetch by IDs mechanism, you need to register it using workload service configuration: + +```xml + + + + + +``` + +You can see an example of this in [Microsoft.AAD.Reporting service config](https://microsoftgraph.visualstudio.com/onboarding/_git/AGS-OnboardingAutomationPipeline?path=%2FMicrosoft.AAD.Reporting_WorkloadConfig.config&version=GBconfig%2Fprd%2FringSMK&_a=contents). + +#### How this works + +> **NOTE**: Simply adding the BulkFetchUri config and implementing the bulk fetch operation lights up the following paging and filtering support. No further work is necessary. + +For a _standard paging request_ using `$select` - for example `GET https://graph.microsoft.com/beta/users?$select=id,displayName,signInActivity`- the first page of results is fetched from the workload that masters the entity type. The IDs from the response are used in the bulk fetch request to any other workloads that master properties in the $select. The results are stitched together and the page of merged results are returned to the caller (with a standard nextlink to the next page of results). You can see how this works using the `$whatif` query parameter - just append this to the GET request above, in [Graph Explorer](https://aka.ms/ge): + +```json +{ + "Description": "Foreach entity obtained from Request1, run Request2 and merge responses:", + "Request1": { + "Description": "Execute HTTP request", + "Uri": "https://graph.windows.net/v2/72f988bf-86f1-41af-91ab-2d7cd011db47/users?$select=id,displayName,id", + "HttpMethod": "GET", + "TargetWorkloadId": "Microsoft.DirectoryServices" + }, + "Request2": { + "Description": "Execute HTTP request", + "Uri": "https://reportingservice.activedirectory.windowsazure.com/auditLogs/userSignInActivity/Default.GetByIds?$select=signInActivity", + "HttpMethod": "GET", + "TargetWorkloadId": "Microsoft.AAD.Reporting" + } +} +``` + +If you want to offer _paging **and** filtering_, this is also possible - with a [limitation](#current-limitations). The order of operation is the same if filtering on properties from Directory Services (like `displayName`, `department` etc). However, if filtering on a property mastered by a different workload, the request goes there first. So for example, adding a filter clause on `signInActivity` and using our `$whatif` query parameter yields: + +```json +{ + "Description": "Foreach entity obtained from Request1, run Request2 and merge responses:", + "Request1": { + "Description": "Execute HTTP request", + "Uri": "https://reportingservice.activedirectory.windowsazure.com/users?$filter=signInActivity%2flastSignInDateTime+le+2020-06-01T00%3a00%3a00Z&$select=signInActivity,id", + "HttpMethod": "GET", + "TargetWorkloadId": "Microsoft.AAD.Reporting" + }, + "Request2": { + "Description": "Execute HTTP request", + "Uri": "https://graph.windows.net//v2/72f988bf-86f1-41af-91ab-2d7cd011db47/getObjectsById?$select=id,displayName", + "HttpMethod": "GET", + "TargetWorkloadId": "Microsoft.DirectoryServices" + } +} +``` + +Here you can see that the order of operation has changed, and in Request2 it's DirectoryServices that's called with it's bulk fetch mechanism. + +> **IMPORTANT**: To support filtering for properties mastered outside of the workload that masters the entity type, all workloads (including the master workload) that contribute to the "composite type" **must** support and be configured with a bulk fetch operation. + +### Current limitations + +1. Filtering on properties from multiple workloads is not supported. +2. Fan-out writes are not supported for composite types: + - POST to the entity type with properties defined in multiple workloads is not supported. Callers need to create the entity first (with properties from the master workload only), followed by a PATCH to add the other properties. **NOTE**: there may be delays before the PATCH operation will work (due to replication/sync delays). + - Similarly PATCH containing properties mastered by multiple workloads is also not supported. + +The fan-out write scenarios may be better supported using the [instant-on mechanism](../../../Service/Proxy/Instant-On). + +## Shared entity types + +For this scenario, API owners in different teams/workloads want to share a common set of schema, or entity types. This provides Microsoft Graph developers with consistent objects and experiences for functionality (that unbeknown to them) is spread across multiple services. Examples candidates for this scenario: + +* Common audit event entity type (or base type) +* M365 unified RBAC APIs has a common entity types for role definitions and role assignments, used by multiple teams (Azure AD, Intune and Exchange). + +### How to + +To implement shared types is pretty simple: + +* One workload CSDL defines the entity type (and all of its properties) +* The other workloads + * define the same entity type, but do not need to define its properties. This **is** different from shared complex types. + * must **not** define any additional properties in the type definition. You cannot extend a shared entity type. This would be counter to shared entity types being the same. +* All workloads (including the one that defines the type) mark the shared entity type as shareable - using the `ags:IsSharedEntity="true"` AGS annotation. +* Do **not** mark any of the shared entity types with `ags:IsMaster="true"` AGS annotation. + +The shared types can be used in their own workload like any other entity type: + +* in navigation properties +* have functions or actions bound to them +* used as a base class for derived types + +### Example + +We'll use the M365 unified RBAC APIs as an example. +Here the Enterprise RBAC workload defines the unified role definition and unified role assignment entity types together with its properties. Both Directory Services and Intune workload also define the same types but without its properties. + +**In Microsoft.EnterpriseRbac.csdl**: + +This workload **fully** defines the `unifiedRoleDefinition` and the `unifiedRoleAssignment` entity types with the sharing annotation. + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +**In Microsoft.DirectoryServices.csdl**: + +This workload **only** defines the `unifiedRoleDefinition` and the `unifiedRoleAssignment` entity types without any properties, but with the sharing annotation. + +```xml + + + + + +``` diff --git a/graph/modeling-patterns/type-namespaces.md b/graph/type-namespaces.md similarity index 96% rename from graph/modeling-patterns/type-namespaces.md rename to graph/type-namespaces.md index 5d370df6..4ee69672 100644 --- a/graph/modeling-patterns/type-namespaces.md +++ b/graph/type-namespaces.md @@ -1,15 +1,11 @@ ---- -title: "Type namespaces" -owner: sanonsen, mastaffo ---- +[[_TOC_]] # Type namespaces Types should be declared in an appropriate namespace. As with traditional compiled libraries, putting types in namespaces creates a better developer experience. -::: warning -Type namespaces are not 1:1 with URL segmentation. For guidance on URL segmentation, see [[Singletons]]. -::: +> Warning
+> Type namespaces are not 1:1 with URL segmentation. For guidance on URL segmentation, see [[Singletons]]. ## Namespace usage @@ -105,8 +101,8 @@ Graph does not enforce namespace ownership. However, namespaces do have key cont Types must exist within a namespace, and workloads must explicitly state which namespace a type exists in. This does not mean that a workload must have their own namespace. A workload could continue to use their internal namespace or explicitly state that the types are in `microsoft.graph`. -| Namespace | Owner | -| --------- | ----- | +| Namespace | Owner | +| --------------------------- | ---------------------------------------------------------------- | | microsoft.graph.callRecords | [IC3 Records Distribution team](mailto:ic3recdist@microsoft.com) | ## Extensibility and cross-referencing diff --git a/graph/common-patterns/webhooks.md b/graph/webhooks.md similarity index 78% rename from graph/common-patterns/webhooks.md rename to graph/webhooks.md index 1411c133..88d85b76 100644 --- a/graph/common-patterns/webhooks.md +++ b/graph/webhooks.md @@ -1,20 +1,17 @@ ---- -title: Adding support for Change Notifications -owner: vibiret ---- +[[_TOC_]] # Adding support for Change Notifications -Microsoft Graph Change Notifications enables application to subscribe to resource changes happening accross Microsoft Graph without having to continuously poll the API for changes. Change notifications can be delivered to the subscriber either via webhooks (the subscriber exposes a notification URL) or via Azure Events Hub (first party only at the moment). For an overview of the general concept, please [refer to the public documentation](https://docs.microsoft.com/en-us/graph/webhooks). +Microsoft Graph Change Notifications enables application to subscribe to resource changes happening across Microsoft Graph without having to continuously poll the API for changes. Change notifications can be delivered to the subscriber either via webhooks (the subscriber exposes a notification URL) or via Azure Events Hub (first party only at the moment). For an overview of the general concept, please [refer to the public documentation](https://docs.microsoft.com/en-us/graph/webhooks). ## Why should you add change notifications support for your entities? -There are different scenarios where customers need to trigger custom logic execution based on changes in Microsoft Graph. These scenarios range from updating UI to reflect changes in the Graph to scenarios that need to analyze data (eg. DLP, compliance, line of business apps...). +There are different scenarios where customers need to trigger custom logic execution based on changes in Microsoft Graph. These scenarios range from updating UI to reflect changes in the Graph to scenarios that need to analyze data (e.g. DLP, compliance, line of business apps...). Today, if your API surface does not support change notifications, the only avenue for customers to implement such scenarios is by **continuously query your API surface**. This increases the complexity and cost of such solutions for customers or makes implementing certain scenarios impossible at scale. More importantly, it greatly increases COGS for the Microsoft Graph as well as your API. ## Architectural overview -![webhooks architecture](../../images/design/webhookarchitecture.svg) +![webhooks architecture](WebHooks-architecture.svg) The diagram above documents a simplified overview of the service components involved in the change notifications infrastructure: @@ -25,7 +22,7 @@ The diagram above documents a simplified overview of the service components invo - Maintains a store of subscriptions. - Validates the `notificationUrl` during subscription creation. - Validates the `notificationUrl` performance on regular basis. - - Coordinates subscriptions CRUD operations with worloads by calling workloads APIs. + - Coordinates subscriptions CRUD operations with workloads by calling workloads APIs. - **Publisher Service**: this service receives any notification emitted by workloads, batches multiple notifications to the same notification URL, and delivers notifications. This service also handles retrying delivery when the subscriber's notification URl is slow and/or not available. This service is owned by the Microsoft Graph Change Tracking Team. - **Workload Service**: this service is the target workload's own API. It maintain it's local subscription store, implements an API for subscriptions CRUD operations and emits notifications. @@ -33,12 +30,13 @@ The diagram above documents a simplified overview of the service components invo ### Update the public documentation -1. Add your resource to [the table of permissions](https://docs.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-beta&tabs=http#permissions). This needs to be done for [GET](https://docs.microsoft.com/en-us/graph/api/subscription-get-subscriptions?view=graph-rest-beta&tabs=http#permissions), [POST](https://docs.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-beta&tabs=http#permissions), [UPDATE](https://docs.microsoft.com/en-us/graph/api/subscription-update-subscriptions?view=graph-rest-beta&tabs=http#permissions) and [DELETE](https://docs.microsoft.com/en-us/graph/api/subscription-delete?view=graph-rest-beta&tabs=http) (\*). +1. Add your resource to [the table of permissions](https://docs.microsoft.com/en-us/graph/api/subscription-update?view=graph-rest-beta&tabs=http#permissions). This needs to be done for [Get](https://docs.microsoft.com/en-us/graph/api/subscription-get?view=graph-rest-beta&tabs=http#permissions), [Create](https://docs.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-beta&tabs=http#permissions), [Update](https://docs.microsoft.com/en-us/graph/api/subscription-update?view=graph-rest-beta&tabs=http#permissions) and [Delete](https://docs.microsoft.com/en-us/graph/api/subscription-delete?view=graph-rest-beta&tabs=http#permissions) (\*). 1. Add the maximum expiration time entry [in the table](https://docs.microsoft.com/en-us/graph/api/resources/subscription?view=graph-rest-beta#maximum-length-of-subscription-per-resource-type) (\*). 1. Add the example [resource paths](https://docs.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-beta&tabs=http#example) (\*). 1. Add the resource in the [overview page](https://docs.microsoft.com/en-us/graph/api/resources/webhooks?view=graph-rest-beta) as well as a [permission](https://docs.microsoft.com/en-us/graph/api/resources/webhooks?view=graph-rest-beta#permissions) (\*). 1. Add an entry into [supported resources](https://docs.microsoft.com/en-us/graph/webhooks#supported-resources) on the concept page (\*\*). -1. Add an entry in the [change log](https://docs.microsoft.com/en-us/graph/changelog), [guidance](../../document/guidelines/changelog.html) +1. Add an entry in the [change log](https://docs.microsoft.com/en-us/graph/changelog), [guidance](../../Document/Guidelines/Changelog) +1. Add an entry in the [latency table](https://docs.microsoft.com/en-us/graph/webhooks#latency) (\*\*). > \*: These pages are available for beta and v1.0, make sure you update the beta pages during the public preview of change notifications support for your API. Make sure you update v1.0 pages when support ships for general availability. Updates for different versions can be done in different pull request. @@ -58,7 +56,7 @@ When creating the SPOT intake, make sure you indicate you are adding change noti - Responsible Org: Deployment Services - Responsible Team: DS C+AI Team -In relevant links, add the [change notifications support wiki](https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/311338/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks) `https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/311338/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks` +In relevant links, add the [change notifications support wiki](https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/313638/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks) `https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/313638/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks` Some of the information that support will require to add it to their internal documentation include: @@ -71,7 +69,7 @@ The subscription service relies on matching the subscription's resource property 1. Gets the entity that matches the resource property. 1. Get the `ags:AddressUrl` property of that entity from the schema. -1. Uses the Fully Qualified Domain Name or hostname of the url to determine which adapter to load. +1. Uses the Fully Qualified Domain Name or hostname of the URL to determine which adapter to load. 1. Uses the adapter to determine the full URL of the workload's subscriptions API and call it. You need to make sure you have: @@ -82,29 +80,29 @@ You need to make sure you have: > The entity referenced by a subscription may be hidden via `ags:IsHidden` if the entity is not a publicly available entity but needs to provide support for change notifications (first-party only scenarios). -If your entity does not meet the requirements listed above, you need to update the schema definition of your entity, or add your entity if it is not defined all together. Please refer to the [guidance](https://msazure.visualstudio.com/One/_wiki/wikis/Microsoft%20Graph%20Partners/55110/Test-Using-VSTS-Repo). +If your entity does not meet the requirements listed above, you need to update the schema definition of your entity, or add your entity if it is not defined all together. Please refer to the [guidance](../../../Rollout/Publish-schema/Test-using-VSTS-repo)Test-using-VSTS-repo). ### Handle subscriptions CRUD operations Subscriptions created by applications are maintained in two separate services, you must provide an API that supports CRUD operations for subscriptions and implement an adapter in the Microsoft Graph subscription service that will relay calls to the former API as described below. -#### The workload's subcription service +#### The workload's subscription service -This service is in charge of reflecting any operation on subscription relayed by the subscription service into the workloads data store. The API is usually colocated with the workload's API. The API you are building to implement subscriptions CRUD operations at a workload's level should be as close as possible to the [public-facing API](https://docs.microsoft.com/en-us/graph/api/resources/subscription?view=graph-rest-beta) to limit the need for payload transformations. You should also consider the following points: +This service is in charge of reflecting any operation on subscription relayed by the subscription service into the workloads data store. The API is usually collocated with the workload's API. The API you are building to implement subscriptions CRUD operations at a workload's level should be as close as possible to the [public-facing API](https://docs.microsoft.com/en-us/graph/api/resources/subscription?view=graph-rest-beta) to limit the need for payload transformations. You should also consider the following points: - Requests to GET existing subscriptions are not proxied to the workload, these are served by the Subscription Store service directly. - When authorizing requests from the Subscription Store service, the actor of the PFT will be the subscription store service, not AGS. -- The Subscription Store service will use your service's DNS name as the resource uri when requesting tokens, so this needs to be registered on your first-party app. +- The Subscription Store service will use your service's DNS name as the resource URI when requesting tokens, so this needs to be registered on your first-party app. - For POST operations, the Microsoft Graph Subscription Service will substitute the following properties that were provided by the client: - - `notificationUrl`: replaced by the url of the Publisher Service that the workload should use to submit new notifications. - > This new URL is region specific allowing the workload to send notifications to the publisher in the corresponding region. Should the URL change for any reason, the Subscription Service will delete and re-create subscriptions for the workload byt calling the workload's subscription service. + - `notificationUrl`: replaced by the URL of the Publisher Service that the workload should use to submit new notifications. + > This new URL is region specific allowing the workload to send notifications to the publisher in the corresponding region. Should the URL change for any reason, the Subscription Service will delete and re-create subscriptions for the workload by calling the workload's subscription service. - `clientState`: replaced by a randomly generated one in order to protect the customer's service. > The subscription and publisher services will also replace the subscription id provided by the workload with a unified subscription id generated by the subscription service during the subscription creation. #### The Microsoft Graph Subscription Service -This service is in charge of maintaining the integrity of subscriptions accross internal services (Microsoft Graph and workloads) and coordinates calls to workloads stores to do so. For the subscription store to be aware of new resources that can be subscribed to and start relaying CRUD operations to the workload's, you need to implement an adapter in the **Subscription Service**: +This service is in charge of maintaining the integrity of subscriptions across internal services (Microsoft Graph and workloads) and coordinates calls to workloads stores to do so. For the subscription store to be aware of new resources that can be subscribed to and start relaying CRUD operations to the workload's, you need to implement an adapter in the **Subscription Service**: - You can see a sample of such implementation [on our repository](https://dev.azure.com/msazure/One/_git/AD-AGSChangeTracking/commit/39038e51f9f9c57481e21d32c7b3243920de9163). - You must specify your workload's application id in the Subscription Publisher service's configuration for tokens to be accepted. @@ -118,7 +116,7 @@ To get started with the repository: 1. Clone your fork to your local machine. 1. Open a shell as admin and navigate to the newly cloned repository. 1. Run `init.cmd`. -1. Run `init.cmd -createshortcut`, this will create a shortcurt on your desktop. +1. Run `init.cmd -createshortcut`, this will create a shortcut on your desktop. 1. Open the shortcut as an administrator. 1. Navigate to `/src/dev/Notifications` (cd). 1. Run `vsmsbuild dirs.proj` to open the projects in Visual Studio. @@ -134,22 +132,22 @@ To get the tests running locally: 1. Then select `Default Processor Architecture`. 1. Then select `X64`. - ![Image](../../images/design/webhooks-vs.png) + ![Image](WebHooks-vs.png) -> 'build' must be run from the src/dev/notifications folder. Any test failures must be re-run in visual studio. A couple tests fail intermittently, running those multiple times should result in all passing. Stylecop failures must be addressed. +> 'build' must be run from the src/dev/notifications folder. Any test failures must be re-run in visual studio. A couple tests fail intermittently, running those multiple times should result in all passing. StyleCop failures must be addressed. > Visual studio 2019 is required. Open the project: src/dev/Notifications> vsmsbuild dirs.proj #### OData query parameters -By default the subscription service will forward OData query paremeters in the resource property. It is up to the workload to decide whether they want to support those or not. +By default the subscription service will forward OData query parameters in the resource property. It is up to the workload to decide whether they want to support those or not. If the workload decides not to support OData query parameters, it should return a valid OData error that explicitly calls out the fact that OData query parameters are not supported for that resource. The subscription service will respond to the initial request with the provided error. If the workload decides to support OData query parameters, it should implement those: -- **\$filter**: if the entity emitting the filter, a notification should be emitted, if not not notification should be emitted. eg for messages `$filter=IsRead eq 'true'` should only emit notifications when the `IsRead` property of the message is `true`. If no filter is provided, all notifcations should be emitted given they match other conditions (changeType, ...). -- **\$select**: if the change is on a property included in the select statement, a notification should be emitted. If the change is not on a property included no notification should be emitted. If no select is provided, all notifcations should be emitted given they match other conditions (changeType, ...). +- **\$filter**: if the entity emitting the filter, a notification should be emitted, if not no notification should be emitted. e.g. for messages `$filter=IsRead eq 'true'` should only emit notifications when the `IsRead` property of the message is `true`. If no filter is provided, all notifications should be emitted given they match other conditions (changeType, ...). +- **\$select**: if the change is on a property included in the select statement, a notification should be emitted. If the change is not on a property included no notification should be emitted. If no select is provided, all notifications should be emitted given they match other conditions (changeType, ...). - **\$top**: not supported in this context. - **\$orderby**: not supported in this context. - **\$expand**: not supported in this context. @@ -162,7 +160,7 @@ If the workload decides to support OData query parameters, it should implement t You must send any notification to the publisher service's using the `notificationUrl` (POST) that was provided by the Microsoft Graph Subscription Service during the subscription creation. -The notification Url will require an `Authorization` request header with the following value `Bearer ` where the bearer token is an app-only JWT obtained from EvoSTS for the Microsoft Graph Change Tracking App Id as audience (0bf30f3b-4a52-48df-9a82-234910c4a086). +The notification URL will require an `Authorization` request header with the following value `Bearer ` where the bearer token is an app-only JWT obtained from EvoSTS for the Microsoft Graph Change Tracking App Id as audience (0bf30f3b-4a52-48df-9a82-234910c4a086). > Note: you need to app your app id in the publisher configuration as outlined in the Pull Request sample provided in [The Microsoft Graph Subscription Service](#the-microsoft-graph-subscription-service) section. @@ -175,7 +173,6 @@ Here is an example payload publishing one notification. "@odata.type": "#Microsoft.OutlookServices.Notification", "Id": null, "SubscriptionExpirationDateTime": "2019-02-14T23:56:30.1307708Z", - "SequenceNumber": 21, "ChangeType": "created", "subscriptionId": "MTE1MTVlYTktMjVkZS00MjY3LWI1YzYtMjg0NzliZmRhYWQ2", "resource": "https://outlook.office365.com/api/beta/Users('userId@tenantId')/Events('')", @@ -198,7 +195,6 @@ Here is an example payload publishing one notification. | @odata.type | OData Type of the notification as represented on the workload | Yes | String | | Id | Unique Id for notification | No | String | | SubscriptionExpirationDateTime | Expiration Date and time of the subscription | Yes | String | -| SequenceNumber | Can be used to allow subscribers to re-order notificiations when they receive them. | No | Number | | ChangeType | Change type of the resource represented by the notification (created,updated,deleted) | Yes | String | | SubscriptionId | Internal subscription Id as used by the workload, the Publisher Service will translate it to a public subscription Id for the subscriber | Yes | String | | Resource | Resource URL on Microsoft Graph. Only exception for Exchange where the Publisher Service translates the value. | Yes | String | @@ -211,16 +207,16 @@ Here is an example payload publishing one notification. > The maximum number of notifications that can be sent at once is 100. Notifications for different subscriptions or tenants can be batched in the same call to the publisher service. -### Lifecycle notifications (preview)(optional) +### Life cycle notifications (preview)(optional) -Lifecycle notifications allow subscribers to get additional notifications whenever a change impacting the subscription itself occurs. When receiving a lifecycle notification, subscribers can choose to try to auto-remediate the issue and/or let the end user know that an action is required from them. This feature is currently in public preview and you can learn more about it's usage in the [public documentation](https://docs.microsoft.com/en-us/graph/webhooks-outlook-authz). +Life cycle notifications allow subscribers to get additional notifications whenever a change impacting the subscription itself occurs. When receiving a life cycle notification, subscribers can choose to try to auto-remedy the issue and/or let the end user know that an action is required from them. This feature is currently in public preview and you can learn more about it's usage in the [public documentation](https://docs.microsoft.com/en-us/graph/webhooks-lifecycle). -You should always send lifecycle notifications if you choose to implement them. Subscribers will receive them only if they provide an additional `lifecycleNotificationUrl` during subscription creation (POST). +You should always send life cycle notifications if you choose to implement them. Subscribers will receive them only if they provide an additional `lifecycleNotificationUrl` during subscription creation (POST). -Lifecycle notifications must be delivered to the `notificationUrl`. They do not include any resource data and must include a `changeType` property (string) for which the valid values are: +Life cycle notifications must be delivered to the `notificationUrl`. They do not include any resource data and must include a `changeType` property (string) for which the valid values are: - `missed`: notifications have not been sent to the subscriber because of a resource change, the subscriber should get the latest version of the resource from Microsoft Graph. -- `subscriptionRemoved`: the subscription has been deleted by the workload either because the resource itself has been deleted (eg a user left the company, the corresponding mailbox has been archived and the subscriptions to the mailbox have been deleted) or because of compliance issues (user's password reset, device out of compliance...). The subscriber should try to get the latest version of the resource from Microsoft Graph and if it still exists, subscribe again. The publisher service will delete the subscription from the subscription service automatically upon recenption of such notification. +- `subscriptionRemoved`: the subscription has been deleted by the workload either because the resource itself has been deleted (e.g. a user left the company, the corresponding mailbox has been archived and the subscriptions to the mailbox have been deleted) or because of compliance issues (user's password reset, device out of compliance...). The subscriber should try to get the latest version of the resource from Microsoft Graph and if it still exists, subscribe again. The publisher service will delete the subscription from the subscription service automatically upon reception of such notification. - `reauthorizationRequired`: the subscription notifications are still delivered to the publisher service by the workload, but the publisher service is holding them until the subscriber completes an [authorization challenge](https://docs.microsoft.com/en-us/graph/webhooks-with-resource-data#authorization-challenge-flow). If the subscriber fails to complete the challenge before the subscription's expiration time, notifications are dropped and the subscription is deleted. If the subscriber completes the authorization challenge in time, pending notifications are delivered. This is automatically generated by the publisher service to ensure that subscribers are still in compliance with access policies when delivering rich notifications (see below). > If the list of event types above does not cover your scenario, please let us know (see How to get help), the specification and public documentation allows for introducing new event types. The additional values cannot be any of `created`, `updated` or `deleted` as those values are reserved for classic notifications. @@ -229,11 +225,11 @@ Lifecycle notifications must be delivered to the `notificationUrl`. They do not ## Rich notifications (preview) -Rich notifications, publicly known as [change notifications with resource data](https://docs.microsoft.com/en-us/graph/webhooks-with-resource-data), build on the fundations layed by change notifications. The major difference between rich notifications and basic notifications being that rich notifications include changed data with the notification delivered to the subscriber. This allows subscribers to understand change context without having to query Microsoft Graph saving them implementation efforts, latency and also saving us COGS. All implementation details provided by basic notifications still apply to rich notifications. +Rich notifications, publicly known as [change notifications with resource data](https://docs.microsoft.com/en-us/graph/webhooks-with-resource-data), build on the foundation of change notifications. The major difference between rich notifications and basic notifications being that rich notifications include changed data with the notification delivered to the subscriber. This allows subscribers to understand change context without having to query Microsoft Graph saving them implementation efforts, latency and also saving us COGS. All implementation details provided by basic notifications still apply to rich notifications. ### Handling rich notifications subscription CRUD requests -Rich notification subscriptions are created the same way as basic notifications subscriptions. In addition to properties documented in the [Handle subscriptions CRUD operations](#handle-subscriptions-CRUD-operations) section, you'll receive the property `includeResourceData` set to `true` as an indicator that the subscriber wants to receive data with the notifications. If the resource does not support sending data, you must return an error. +Rich notification subscriptions are created the same way as basic notifications subscriptions. In addition to properties documented in the [Handle subscriptions CRUD operations](#handle-subscriptions-crud-operations) section, you'll receive the property `includeResourceData` set to `true` as an indicator that the subscriber wants to receive data with the notifications. If the resource does not support sending data, you must return an error. > Note: if you decide to encrypt the resource data before sending it to the publisher service (see below), you will also receive the following additional properties: `encryptionCertificate` which is a base64 encoded certificate public key, `encryptionCertificateId` which is a subscriber-defined identifier for the certificate and must be attached with the encrypted content. @@ -246,7 +242,7 @@ The changed data attached to the notification is always encrypted when delivered Workloads have two main choices when it comes to encrypting the data: - You can delegate the encryption work to the publisher service, simply add any data property to the resourceData object. The publisher will take any property that is not whitelisted (id, odata.type, odata.id), remove them from the resourceData object and create the encrypted content with it. -- You can implement the encryption of the content yourself before submitting the notification to the publisher service. This is especially usefull when you must respect data privacy and/or residency standards as it ensures that nobody else besides the workload and the subscriber can read the data. +- You can implement the encryption of the content yourself before submitting the notification to the publisher service. This is especially useful when you must respect data privacy and/or residency standards as it ensures that nobody else besides the workload and the subscriber can read the data. The choice should be carefully considered ahead of time as there's no migration plan in place today that supports changing "who encrypts the payload" once notifications start being sent. @@ -254,20 +250,20 @@ The encryption boundary is determined by the `RichNotificationsPayloadEncryption #### Encrypting resource data on workload -Should you choose to encrypt the payload, we provide a [utility class](https://msazure.visualstudio.com/One/_git/AD-AGSChangeTracking?path=%2Fsrc%2Fdev%2FNotifications%2FNotifications.Azure%2FEncryptor%2FEncryptor.cs&version=GBmaster) that helps you encrypt the content, the key and generate the signature. To get this library add the `Microsoft.Online.AggregatorService.Encryptor` from our [nuget feed](https://msazure.visualstudio.com/One/_packaging?_a=feed&feed=Official). +Should you choose to encrypt the payload, we provide a [utility class](https://dev.azure.com/msazure/One/_git/AD-AGSChangeTracking?path=%2Fsrc%2Fdev%2FNotifications%2FNotifications.Azure%2FEncryptor%2FEncryptor.cs&version=GBmaster) that helps you encrypt the content, the key and generate the signature. To get this library add the `Microsoft.Online.AggregatorService.Encryptor` from our [nuget feed](https://dev.azure.com/msazure/One/_packaging?_a=feed&feed=Official). > Note: the library is only available for dotnet standard today. The library will return 3 out of 5 required properties to add to the `encryptedContent` object: -- `data`: the encrypted (using the single-use generated symetric key) and base64 encoded representation of the JSON value you provided as input. +- `data`: the encrypted (using the single-use generated symmetric key) and base64 encoded representation of the JSON value you provided as input. - `dataSignature`: the HMAC-SHA256 the digital signature of the `data` value. -- `dataKey`: the encrypted (using the subscriber's provided public key) and base64 encoded single-use generated symetric key to be used to validate signature and decrypt the `data`. +- `dataKey`: the encrypted (using the subscriber's provided public key) and base64 encoded single-use generated symmetric key to be used to validate signature and decrypt the `data`. You are also required to add the following properties to the `encryptedContent` object before attaching it to the notification object: - `encryptionCertificateId`: the subscriber generated identified for the certificate provided during the subscription creation. -- `encryptionCertificateThumbprint`: the digital thumbprint of the certificate used for encryption of the single-use generated symetric key. +- `encryptionCertificateThumbprint`: the digital thumbprint of the certificate used for encryption of the single-use generated symmetric key. > The data, before it's encrypted, must be a JSON object with the same schema public schema of the entity the notification is emitted for. If any **\$select** query parameter is included with the resource property of the subscription object, only properties included in the select statement should be included. For more informations refer to [OData query parameters](#odata-query-parameters). @@ -321,10 +317,10 @@ The subscription service defines throttling rules at the AGS level that will be #### Local development flow -If you are building the subcriptions CRUD API in your workload and you want to validate that it respects the expected contract on your local dev machine, you can leverage unit tests to perform such validation. : +If you are building the subscriptions CRUD API in your workload and you want to validate that it respects the expected contract on your local dev machine, you can leverage unit tests to perform such validation. : - Navigate to the `src/dev/Notifications/ExtendedTests/ExtendedTests.EndToEnd/E2ETests.cs` class on your local copy of the service repository. -- Update the `ExchangeEndpointUrl` property value by the local url of the API you are developping ([ref](https://dev.azure.com/msazure/One/_git/AD-AGSChangeTracking?path=%2Fsrc%2Fdev%2FNotifications%2FExtendedTests%2FExtendedTests.EndToEnd%2FE2ETests.cs&version=GBmaster&line=127&lineEnd=127&lineStartColumn=30&lineEndColumn=49&lineStyle=plain)). +- Update the `ExchangeEndpointUrl` property value by the local URL of the API you are developing ([ref](https://dev.azure.com/msazure/One/_git/AD-AGSChangeTracking?path=%2Fsrc%2Fdev%2FNotifications%2FExtendedTests%2FExtendedTests.EndToEnd%2FE2ETests.cs&version=GBmaster&line=127&lineEnd=127&lineStartColumn=30&lineEndColumn=49&lineStyle=plain)). - Run the tests. > These unit tests are designed to work with mocked APIs and will send invalid access tokens, you should disable authorization for the subscriptions part of your API. @@ -338,13 +334,13 @@ You can also leverage [our public sample](https://github.com/microsoftgraph/aspn ### First-Party only notifications Microsoft Graph change notifications provides support for notifications available only for first party applications. -The following table lists the resources availabel for susbcription: +The following table lists the resources available for subscription: -| Resource | Description | -| ---------------------- | ----------------------------------------------------------------------------- | -| AllTenantAssignedPlans | Triggers a notification whenever plans assignement are updated on any tenant. | -| AllTenantTenantDelete | Triggers a notification whenever a tenant gets deleted. | -| AllTenantUserDelete | Triggers a notification whenever a user gets deleted on any tenant. | +| Resource | Description | +| ---------------------- | ---------------------------------------------------------------------------- | +| AllTenantAssignedPlans | Triggers a notification whenever plans assignment are updated on any tenant. | +| AllTenantTenantDelete | Triggers a notification whenever a tenant gets deleted. | +| AllTenantUserDelete | Triggers a notification whenever a user gets deleted on any tenant. | #### How to create a first party subscription @@ -362,7 +358,7 @@ The following table lists the resources availabel for susbcription: Should you need any help during your design and implementation, there are a couple of ways you can reach out: - [Stackoverflow.com](https://stackoverflow.com): for any question that does not contain confidential, internal or customer related information. Example: questions about ASP.NET core MVC, questions about the OData libraries, etc. -- [Internal Stackoverflow](https://stackoverflow.microsoft.com): for any question that might contain confidential or internal information. Example: how do I configure the routing in AGS to do... -- [Teams: Microsoft Graph > Webhooks](https://teams.microsoft.com/l/channel/19%3a4139ee2d50a54a14a75365d505d27364%40thread.skype/Webhooks?groupId=6d279915-6f5d-452c-895b-7f4f82038843&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47): for any question that contains details really specific to your implementation, escalation of questions left unanswered on one of the stackoverflow platforms, ... +- [Internal StackOverflow](https://stackoverflow.microsoft.com): for any question that might contain confidential or internal information. Example: how do I configure the routing in AGS to do... +- [Teams: Microsoft Graph > Webhooks](https://teams.microsoft.com/l/channel/19%3a4139ee2d50a54a14a75365d505d27364%40thread.skype/Webhooks?groupId=6d279915-6f5d-452c-895b-7f4f82038843&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47): for any question that contains details really specific to your implementation, escalation of questions left unanswered on one of the StackOverflow platforms, ... Ask: please refrain from asking questions directly to the engineering team via either emails, Teams chat etc... This does not scale and it doesn't capture the question (and answer) for other people that might have the same question as you. You should always try to ask a question on stack overflow first before reaching out on the Teams channel. From d534cc184429c9d4b8369b38257755ac3ad73022 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sat, 27 Mar 2021 14:59:36 -0400 Subject: [PATCH 068/729] Added missing descriptions --- graph/guidelines.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/graph/guidelines.md b/graph/guidelines.md index d37a939f..1e902446 100644 --- a/graph/guidelines.md +++ b/graph/guidelines.md @@ -167,7 +167,7 @@ The following table of contents links back to primary guidelines where there are #### 9a.1. Noun Resources -While HTTP defines no constraints on how different resources are related together, it does encourage the use of URL path segment hierarchies to convey a relationship. In addition to the hierarchy of resources, there are also lifetime relationships between resources, the notions of [singletons, entitySets, entities, complex types and navigation properties](entity-complex.md) make it possible to define a set of lifetime relationships between resources. +While HTTP defines no constraints on how different resources are related together, it does encourage the use of URL path segment hierarchies to convey a relationship. In addition to the hierarchy of resources, there are also lifetime relationships between resources, the notions of [singletons, entitySets, entities, complex types and navigation properties](entity-complex) make it possible to define a set of lifetime relationships between resources. The notion of [containment](containment) is important in understanding how lifetime relationships are defined. #### 9a.2. Verb Resources @@ -175,7 +175,7 @@ Noun-based resources are not always the best fit for meeting the requirements of #### 9a.3. Resource Modeling -There are a number of principles to be aware of when modeling resources for Microsoft Graph. [modeling variants](modeling-variants) +There are a number of principles to be aware of when modeling resources for Microsoft Graph. [Modeling variants](modeling-variants) is important when resources have have a subset of common properties and behavior. ### 10. Deltas @@ -189,7 +189,8 @@ The ability to track changes (pull) occuring in the data exposed by Microsoft Gr ### 11.9. Omitting Properties -blah-blah [Omitting properties](ommitting-properties) +For scenarios where the server contains business logic that determines if a property value should be returned, or not, to the client, a returned representation can be annotated to indicate where properties are [omitted](ommitting-properties). + ### 13. Long running operations Long running operations are mostly unchanged. The most significant difference is that instead of using `Operation-Location` as the header to point to the Operation, the use of the standard `Location` header is recommended. In hybrid scenarios, a `Content-Location` header can be used to indicate the URL of the created resource and the 202 response can contain a payload. Details of the diffences are described in detail in the [Long Running Operations](long-running-operations) document. @@ -201,7 +202,7 @@ The ability to get notified (push) when a change occurs in the data exposed by M ### 17.10. Type Namespaces -blah blah [type namespaces](type-namespaces) +Microsoft Graph model types can be declared within a [type namespaces](type-namespaces) to reduce the need to prefix types with a qualifier to ensure uniqueness. ### 18.2. Additional resources From f0b8a4a4fba67aee361d0d647e405eb7347867f5 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 28 Mar 2021 19:12:47 -0400 Subject: [PATCH 069/729] Updates to graph guidelines --- graph/Functions-and-actions.md | 19 ++++++++----------- graph/containment.md | 5 ----- graph/entity-complex.md | 14 +++++--------- graph/guidelines.md | 6 +++--- graph/long-running-operations.md | 4 +--- graph/navigation-containment.md | 7 ------- graph/odata-types-primer.md | 6 ------ graph/omitting-properties.md | 7 +------ graph/webhooks.md | 4 +--- 9 files changed, 19 insertions(+), 53 deletions(-) delete mode 100644 graph/containment.md delete mode 100644 graph/navigation-containment.md delete mode 100644 graph/odata-types-primer.md diff --git a/graph/Functions-and-actions.md b/graph/Functions-and-actions.md index 945bd4c9..03e191a0 100644 --- a/graph/Functions-and-actions.md +++ b/graph/Functions-and-actions.md @@ -1,15 +1,12 @@ -[[_TOC_]] - -# Functions and Actions - -The [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html), beside many other things, provides a way to describe the structure of the requests and responses of an OData service via the [Common Schema Definition Language](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html) (CSDL). +# Functions and Actions ## Functions -The standard [defines](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Function) functions as: +In Graph APIs, functions are defined as: + >service-defined operations that MUST NOT have observable side effects and MUST return a single instance or collection of instances of any type. -Please see the OData standard page to get more details on functions. +Functions use HTTP GET method. ### Example @@ -22,10 +19,10 @@ Please see the OData standard page to get more details on functions. ## Actions -The standard [defines](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Action) actions as: +Actions are defined as: >service-defined operations that MAY have observable side effects and MAY return a single instance or a collection of instances of any type. -Please see the OData standard for more details on actions. +Actions use HTTP POST method. ### Example @@ -45,7 +42,7 @@ Bound actions and functions are invoked on resources matching the type of the bi ## Overloads -Both actions and functions support overloading, meaning a schema may contain multiple actions or functions with the same name. See OData [standard](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionOverloads) for more information and rules. +Both actions and functions support overloading, meaning a schema may contain multiple actions or functions with the same name. ## Parameters @@ -55,7 +52,7 @@ As Graph only supports bound actions and functions, all must have at least one p - The parameter name must be unique within the overload. - The parameter must specify a type. -The overload rules as per the OData [standard](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionOverloads) apply when adding parameters to actions and functions. +Overloaded functions MUST have the same return type, a unique set of parameter names and a unique ordered set of parameter types. ### Optional Parameters diff --git a/graph/containment.md b/graph/containment.md deleted file mode 100644 index 36b36ba1..00000000 --- a/graph/containment.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Containment ---- - -# Coming soon diff --git a/graph/entity-complex.md b/graph/entity-complex.md index f853cbc6..477cf003 100644 --- a/graph/entity-complex.md +++ b/graph/entity-complex.md @@ -1,23 +1,19 @@ ---- -title: Entity Types and Complex Types ---- +# Entity Types and Complex Types -# Entity Types and Complex Types +Microsoft Graph uses entities and complex types as way to describe the structure of the requests and responses of an API. -The [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html), beside many other things, provides a way to describe the structure of the requests and responses of an OData service via the [Common Schema Definition Language](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html) (CSDL). - -CSDL defines a few ways to define [types](https://en.wikipedia.org/wiki/Data_type) and the most prominent are Entity Types and Complex Types. These two have some similarities and some differences that we are going to explore in this article. +Entity Types and Complex Types have some similarities and some differences that we are going to explore in this article. ## Entity Types -Entity Types are that most common way to define the structure of the requests and responses of an OData service. The standard [defines](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_EntityType): +Entity Types are that most common way to define the structure of the requests and responses of a Graph service. > Entity Types are nominal structured types with a key that consists of one or more references to structural properties. An entity type is the template for an entity: any uniquely identifiable record such as a customer or order.. There is three things to note in that (arguably terse) definition: - By "structured" the standard means that an Entity Type is defined by enumerating its (typed) **properties**. -- "nominal" just refers to the fact that the type **has a name** (a name in the CSDL schema). +- "nominal" just refers to the fact that the type **has a name**. - And the most important piece in the context of this article is that an Entity Type declares a **key property** and the consequence is that objects of this type can be uniquely identified through this key. In Microsoft Graph that key is currently always the property named "id". The standards allows more variation and Microsoft Graph might also relax this constraint in the future. How they key is used to identify an object is a bit out of the scope of this document. For now it should suffice to say, that it is used as part of the URL to "name" an individual object. For Example in the URL https://localhost/api/authors/50 (or https://localhost/api/authors(50) ), the 50 is the key of an object. diff --git a/graph/guidelines.md b/graph/guidelines.md index 1e902446..c1e33808 100644 --- a/graph/guidelines.md +++ b/graph/guidelines.md @@ -2,9 +2,9 @@ ## Introduction -The Graph REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Graph guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Graph-specific guidance and additional details. +The Graph REST API guidelines are an extension of the [Microsoft REST API guidelines](../guidelines). Readers of this document are assumed to be also reading the [Microsoft REST API guidelines](../guidelines) and be familiar with them. Graph guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Graph-specific guidance and additional details. -The following table of contents links back to primary guidelines where there are no differences in Graph guidelines. Where differences exist, the section heading is **bold**. +The following table of contents links back to the primary guidelines where there are no differences in Graph guidelines. Where differences exist, the section heading is **bold**. ## 2. Table of contents @@ -167,7 +167,7 @@ The following table of contents links back to primary guidelines where there are #### 9a.1. Noun Resources -While HTTP defines no constraints on how different resources are related together, it does encourage the use of URL path segment hierarchies to convey a relationship. In addition to the hierarchy of resources, there are also lifetime relationships between resources, the notions of [singletons, entitySets, entities, complex types and navigation properties](entity-complex) make it possible to define a set of lifetime relationships between resources. The notion of [containment](containment) is important in understanding how lifetime relationships are defined. +While HTTP defines no constraints on how different resources are related together, it does encourage the use of URL path segment hierarchies to convey a relationship. In addition to the hierarchy of resources, there are also lifetime relationships between resources, the notions of [singletons, entitySets, entities, complex types and navigation properties](entity-complex) make it possible to define a set of lifetime relationships between resources. #### 9a.2. Verb Resources diff --git a/graph/long-running-operations.md b/graph/long-running-operations.md index d52c6e51..f063d4ad 100644 --- a/graph/long-running-operations.md +++ b/graph/long-running-operations.md @@ -1,6 +1,4 @@ -[[_TOC_]] - -# Long-running operations +# Long-running operations Long running operations, sometimes called async operations, tend to mean different things to different people. This section sets forth guidance around different types of long running operations, and describes the wire protocols and best practices for these types of operations. diff --git a/graph/navigation-containment.md b/graph/navigation-containment.md deleted file mode 100644 index 32e6a395..00000000 --- a/graph/navigation-containment.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Navigation Properties and Containment ---- - -# Navigation Properties and Containment - -Coming soon diff --git a/graph/odata-types-primer.md b/graph/odata-types-primer.md deleted file mode 100644 index c7cb7e41..00000000 --- a/graph/odata-types-primer.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "OData type system primer" -owner: mastaffo ---- - -# Coming soon diff --git a/graph/omitting-properties.md b/graph/omitting-properties.md index 018e78f5..a2ffa174 100644 --- a/graph/omitting-properties.md +++ b/graph/omitting-properties.md @@ -1,9 +1,4 @@ -[[_TOC_]] - -# RFC: Omitting properties - -> Warning
Review period ends 10/10/2018
-> +# Omitting properties There are scenarios where the server contains business logic that determines if a property value should be returned, or not, to the client. Even when the client explicitly requests the property, it may be purposefully omitted from the response by the server. This section provides guidance on how the server should explicitly represent omitted properties in the response. diff --git a/graph/webhooks.md b/graph/webhooks.md index 88d85b76..fb635426 100644 --- a/graph/webhooks.md +++ b/graph/webhooks.md @@ -1,6 +1,4 @@ -[[_TOC_]] - -# Adding support for Change Notifications +# Adding support for Change Notifications Microsoft Graph Change Notifications enables application to subscribe to resource changes happening across Microsoft Graph without having to continuously poll the API for changes. Change notifications can be delivered to the subscriber either via webhooks (the subscriber exposes a notification URL) or via Azure Events Hub (first party only at the moment). For an overview of the general concept, please [refer to the public documentation](https://docs.microsoft.com/en-us/graph/webhooks). From 344664c158395589f8a6b508b67c163fc210e266 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 30 Mar 2021 16:41:07 -0400 Subject: [PATCH 070/729] - adds dictionary usage guidelines for graph --- graph/dictionary/client-guidance.md | 77 ++++++++++++ graph/dictionary/index.md | 187 ++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 graph/dictionary/client-guidance.md create mode 100644 graph/dictionary/index.md diff --git a/graph/dictionary/client-guidance.md b/graph/dictionary/client-guidance.md new file mode 100644 index 00000000..fd7b2c20 --- /dev/null +++ b/graph/dictionary/client-guidance.md @@ -0,0 +1,77 @@ +# Dictionary types + +The client guidance is a collection of additional information provided to SDK implementers and client applications. This information is meant to help understand how various guidelines and concept translate in their world and clarify a few unknowns. You should always read the corresponding guideline first to get a context understanding. + +[Read the guideline](./index.md). + +## OpenAPI example + +The following json-schema/OpenAPI example defines a dictionary of which values will by of type **RoleSettings**. + +In **components** in **schemas**: + +```json +{ + "roleSettings": { + "type": "object", + "properties": { + "domain": { + "type": "string" + } + } + } + } +} +``` + +```json +{ + "type": "object", + "patternProperties": { + ".*": { + "$ref": "#/components/schemas/roleSettings" + }, + "additionalProperties": false + } +} +``` + +## SDK Support + +SDKs need to provide support for dictionary types so SDK consummers get a delightful development experience. Examples are provided below for different languages. Other aspects need to be taken into considerations: + +- Dictionaries support OData annotations (values prefixed with **@OData**), such annotations should not be inserted directly in the dictionary but rather in the additional properties manager. +- Dictionary types can inherit another dictionary type, this inheritance must be respected. +- Dictionary values can be of union types, if the target langauge doesn't support union types, a wrapper type should be generated as backward compatible solution with properties for each type of the union. + +### Dotnet + +```CSharp +Dictionary +``` + +### Java + +```Java +Map +``` + +### JavaScript/TypeScript + +```TypeScript +Map +``` + +or + +```JavaScript +{ + [key: string]: {value: RoleSettings} +} +``` + +## Request builder generation annotation + +By default SDKs are not required to contain a set of request builders to run CRUD requests on entries in the dictionary. The dictionary will be updated as a whole by consummers by sending requests to the parent entity. + +If a **SupportedHttpMethod** annotation is specified for the dictionary type, request builders should be generated to allow consummers to automically update the entries. diff --git a/graph/dictionary/index.md b/graph/dictionary/index.md new file mode 100644 index 00000000..fb2fdce7 --- /dev/null +++ b/graph/dictionary/index.md @@ -0,0 +1,187 @@ +# Dictionary types + +Dictionaries, sometimes called maps, are a collection of name-value pairs. They allow dynamic data sets to be accessed in a systematic manner and are a good compromise between a strictly defined ahead of time structure with all its named properties and between a loosely defined dynamic object (i.e. OData OpenTypes). + +More information: + +- [OData reference](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md#dictionary) + +## When to use dictionary types + +Before using a dictionary type in your API definition make sure your scenario fits the following criteria: + +- The data values MUST be related to one another semantically as a collection. +- The value types MUST be a primitive type or is a **ComplexType**. Mixed primitive types are not allowed. +- The client MUST define the keys of this type. As opposed to the service defining it in advance. + +### Alternatives to consider + +- [Open extensions](https://docs.microsoft.com/en-us/graph/extensibility-open-users) when you want to provide clients the ability to extend Microsoft Graph. +- [Open types](https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/use-open-types-in-odata-v4) when your data is not a collection in nature. +- [Complex types](https://docs.microsoft.com/en-us/odata/webapi/complextypewithnavigationproperty) when the set of data values are known. + +## JSON payload example + +The following example illustrates the resulting JSON for a property of dictionary type. The parent object has been ommitted for brievety. + +```json +{ + "author": { + "domain": "contoso" + }, + "maintainer": { + "domain": "fabrikam" + }, + "architect": { + "domain": "adventureWorks" + } +} +``` + +## HTTP calls examples + +In this set of examples we're modeling a **roles** property of dictionary type on the user entity which is exposed by the users entity set. + +### Getting an entry from the dictionary + +```HTTP +GET https://graph.microsoft.com/v1.0/users/10/roles/author +``` + +Reponse: + +```json +{ + "domain": "contoso" +} +``` + +### Getting the dictionary + +```HTTP +GET https://graph.microsoft.com/v1.0/users/10/roles +``` + +Reponse: + +```json +{ + "author": { + "domain": "contoso" + }, + "maintainer": { + "domain": "fabrikam" + }, + "architect": { + "domain": "adventureWorks" + } +} +``` + +### Getting the entity with the dictionary + +```HTTP +GET https://graph.microsoft.com/v1.0/users/10 +``` + +Reponse: + +```json +{ + "id": "10", + "displayName": "Jane Smith", + "roles": { + "author": { + "domain": "contoso" + }, + "maintainer": { + "domain": "fabrikam" + }, + "architect": { + "domain": "adventureWorks" + } + } +} +``` + +### Creating an entry in the dictionary + +```HTTP +POST https://graph.microsoft.com/v1.0/users/10/roles/author + +{ + "domain": "contoso" +} +``` + +### Updating the dictionary + +```HTTP +PATCH https://graph.microsoft.com/v1.0/users/10/roles + +{ + "author": { + "domain": "contoso1" + }, + "maintainer": { + "domain": "fabrikam1" + }, + "reviewer": { + "domain": "fabrikam" + }, + "architect": null +} +``` + +> Note: setting one of the keys to **null** deletes it from the dictionary. +> Note: the domain values for the existing author and maintainer entries will get udpated. +> Note: the reviewer entry will be inserted in the dictionary. + +### Updating an entry in the dictionary + +```HTTP +PATCH https://graph.microsoft.com/v1.0/users/10/roles/author + +{ + "domain": "fabrikam" +} +``` + +### Deleting an entry from the dictionary + +```HTTP +DELETE https://graph.microsoft.com/v1.0/users/10/roles/author +``` + +## CDSL example + +The following example defines a complex type **roleSettings** as well as a dictionary of which the key will be a string and the value a **roleSettings**. + +```xml + + + + + + + + + microsoft.graph.roleSettings + + + + + GET + PATCH + DELETE + POST + + + +``` + +## Additional information + +[SDK implementation guidance](./client-guidance.md). From 7026798ccb0eecd3b3176ecddb90d74f16212fcb Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Tue, 30 Mar 2021 16:43:40 -0400 Subject: [PATCH 071/729] updates to remove Odata references --- graph/shared-types.md | 4 +--- graph/type-namespaces.md | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/graph/shared-types.md b/graph/shared-types.md index 0d9bdd03..eda4cafc 100644 --- a/graph/shared-types.md +++ b/graph/shared-types.md @@ -1,6 +1,4 @@ -[[_TOC_]] - -# Overview +# Overview This topic describes three shared types scenarios: diff --git a/graph/type-namespaces.md b/graph/type-namespaces.md index 4ee69672..27736f34 100644 --- a/graph/type-namespaces.md +++ b/graph/type-namespaces.md @@ -1,6 +1,4 @@ -[[_TOC_]] - -# Type namespaces +# Type namespaces Types should be declared in an appropriate namespace. As with traditional compiled libraries, putting types in namespaces creates a better developer experience. From abc13a9124b6031dc15cfd00e3c6313dbdec7664 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Tue, 30 Mar 2021 16:57:19 -0400 Subject: [PATCH 072/729] Added reference to dictionary document --- graph/guidelines.md | 276 ++++++++++++++++++++++---------------------- 1 file changed, 139 insertions(+), 137 deletions(-) diff --git a/graph/guidelines.md b/graph/guidelines.md index c1e33808..828bab4a 100644 --- a/graph/guidelines.md +++ b/graph/guidelines.md @@ -9,154 +9,154 @@ The following table of contents links back to the primary guidelines where there ## 2. Table of contents -- [Microsoft REST API Guidelines Working Group](../guidelines#microsoft-rest-api-guidelines-working-group) -- [1. Abstract](../guidelines#1-abstract) -- [2. Table of contents](../guidelines#2-table-of-contents) -- [3. Introduction](../guidelines#3-introduction) - - [3.1. Recommended reading](../guidelines#31-recommended-reading) -- [4. Interpreting the guidelines](../guidelines#4-interpreting-the-guidelines) - - [4.1. Application of the guidelines](../guidelines#41-application-of-the-guidelines) - - [4.2. Guidelines for existing services and versioning of services](../guidelines#42-guidelines-for-existing-services-and-versioning-of-services) - - [4.3. Requirements language](../guidelines#43-requirements-language) - - [4.4. License](../guidelines#44-license) -- [5. Taxonomy](../guidelines#5-taxonomy) - - [5.1. Errors](../guidelines#51-errors) - - [5.2. Faults](../guidelines#52-faults) - - [5.3. Latency](../guidelines#53-latency) - - [5.4. Time to complete](../guidelines#54-time-to-complete) - - [5.5. Long running API faults](../guidelines#55-long-running-api-faults) -- [6. Client guidance](../guidelines#6-client-guidance) - - [6.1. Ignore rule](../guidelines#61-ignore-rule) - - [6.2. Variable order rule](../guidelines#62-variable-order-rule) - - [6.3. Silent fail rule](../guidelines#63-silent-fail-rule) -- [7. Consistency fundamentals](../guidelines#7-consistency-fundamentals) - - [7.1. URL structure](../guidelines#71-url-structure) - - [7.2. URL length](../guidelines#72-url-length) - - [7.3. Canonical identifier](../guidelines#73-canonical-identifier) - - [7.4. Supported methods](../guidelines#74-supported-methods) - - [7.4.1. POST](../guidelines#741-post) - - [7.4.2. PATCH](../guidelines#742-patch) - - [7.4.3. Creating resources via PATCH (UPSERT semantics)](../guidelines#743-creating-resources-via-patch-upsert-semantics) - - [7.4.4. Options and link headers](../guidelines#744-options-and-link-headers) - - [7.5. Standard request headers](../guidelines#75-standard-request-headers) - - [7.6. Standard response headers](../guidelines#76-standard-response-headers) - - [7.7. Custom headers](../guidelines#77-custom-headers) - - [7.8. Specifying headers as query parameters](../guidelines#78-specifying-headers-as-query-parameters) - - [7.9. PII parameters](../guidelines#79-pii-parameters) - - [7.10. Response formats](../guidelines#710-response-formats) - - [7.10.1. Clients-specified response format](../guidelines#7101-clients-specified-response-format) - - [7.10.2. Error condition responses](../guidelines#7102-error-condition-responses) - - [7.11. HTTP Status Codes](../guidelines#711-http-status-codes) - - [7.12. Client library optional](../guidelines#712-client-library-optional) -- [8. CORS](../guidelines#8-cors) - - [8.1. Client guidance](../guidelines#81-client-guidance) - - [8.1.1. Avoiding preflight](../guidelines#811-avoiding-preflight) - - [8.2. Service guidance](../guidelines#82-service-guidance) -- [9. Collections](../guidelines#9-collections) - - [9.1. Item keys](../guidelines#91-item-keys) - - [9.2. Serialization](../guidelines#92-serialization) - - [9.3. Collection URL patterns](../guidelines#93-collection-url-patterns) - - [9.3.1. Nested collections and properties](../guidelines#931-nested-collections-and-properties) - - [9.4. Big collections](../guidelines#94-big-collections) - - [9.5. Changing collections](../guidelines#95-changing-collections) - - [9.6. Sorting collections](../guidelines#96-sorting-collections) - - [9.6.1. Interpreting a sorting expression](../guidelines#961-interpreting-a-sorting-expression) - - [9.7. Filtering](../guidelines#97-filtering) - - [9.7.1. Filter operations](../guidelines#971-filter-operations) - - [9.7.2. Operator examples](../guidelines#972-operator-examples) - - [9.7.3. Operator precedence](../guidelines#973-operator-precedence) - - [9.8. Pagination](../guidelines#98-pagination) - - [9.8.1. Server-driven paging](../guidelines#981-server-driven-paging) - - [9.8.2. Client-driven paging](../guidelines#982-client-driven-paging) - - [9.8.3. Additional considerations](../guidelines#983-additional-considerations) - - [9.9. Compound collection operations](../guidelines#99-compound-collection-operations) +- [Microsoft REST API Guidelines Working Group](../guidelines.md#microsoft-rest-api-guidelines-working-group) +- [1. Abstract](../guidelines.md#1-abstract) +- [2. Table of contents](../guidelines.md#2-table-of-contents) +- [3. Introduction](../guidelines.md#3-introduction) + - [3.1. Recommended reading](../guidelines.md#31-recommended-reading) +- [4. Interpreting the guidelines](../guidelines.md#4-interpreting-the-guidelines) + - [4.1. Application of the guidelines](../guidelines.md#41-application-of-the-guidelines) + - [4.2. Guidelines for existing services and versioning of services](../guidelines.md#42-guidelines-for-existing-services-and-versioning-of-services) + - [4.3. Requirements language](../guidelines.md#43-requirements-language) + - [4.4. License](../guidelines.md#44-license) +- [5. Taxonomy](../guidelines.md#5-taxonomy) + - [5.1. Errors](../guidelines.md#51-errors) + - [5.2. Faults](../guidelines.md#52-faults) + - [5.3. Latency](../guidelines.md#53-latency) + - [5.4. Time to complete](../guidelines.md#54-time-to-complete) + - [5.5. Long running API faults](../guidelines.md#55-long-running-api-faults) +- [6. Client guidance](../guidelines.md#6-client-guidance) + - [6.1. Ignore rule](../guidelines.md#61-ignore-rule) + - [6.2. Variable order rule](../guidelines.md#62-variable-order-rule) + - [6.3. Silent fail rule](../guidelines.md#63-silent-fail-rule) +- [7. Consistency fundamentals](../guidelines.md#7-consistency-fundamentals) + - [7.1. URL structure](../guidelines.md#71-url-structure) + - [7.2. URL length](../guidelines.md#72-url-length) + - [7.3. Canonical identifier](../guidelines.md#73-canonical-identifier) + - [7.4. Supported methods](../guidelines.md#74-supported-methods) + - [7.4.1. POST](../guidelines.md#741-post) + - [7.4.2. PATCH](../guidelines.md#742-patch) + - [7.4.3. Creating resources via PATCH (UPSERT semantics)](../guidelines.md#743-creating-resources-via-patch-upsert-semantics) + - [7.4.4. Options and link headers](../guidelines.md#744-options-and-link-headers) + - [7.5. Standard request headers](../guidelines.md#75-standard-request-headers) + - [7.6. Standard response headers](../guidelines.md#76-standard-response-headers) + - [7.7. Custom headers](../guidelines.md#77-custom-headers) + - [7.8. Specifying headers as query parameters](../guidelines.md#78-specifying-headers-as-query-parameters) + - [7.9. PII parameters](../guidelines.md#79-pii-parameters) + - [7.10. Response formats](../guidelines.md#710-response-formats) + - [7.10.1. Clients-specified response format](../guidelines.md#7101-clients-specified-response-format) + - [7.10.2. Error condition responses](../guidelines.md#7102-error-condition-responses) + - [7.11. HTTP Status Codes](../guidelines.md#711-http-status-codes) + - [7.12. Client library optional](../guidelines.md#712-client-library-optional) +- [8. CORS](../guidelines.md#8-cors) + - [8.1. Client guidance](../guidelines.md#81-client-guidance) + - [8.1.1. Avoiding preflight](../guidelines.md#811-avoiding-preflight) + - [8.2. Service guidance](../guidelines.md#82-service-guidance) +- [9. Collections](../guidelines.md#9-collections) + - [9.1. Item keys](../guidelines.md#91-item-keys) + - [9.2. Serialization](../guidelines.md#92-serialization) + - [9.3. Collection URL patterns](../guidelines.md#93-collection-url-patterns) + - [9.3.1. Nested collections and properties](../guidelines.md#931-nested-collections-and-properties) + - [9.4. Big collections](../guidelines.md#94-big-collections) + - [9.5. Changing collections](../guidelines.md#95-changing-collections) + - [9.6. Sorting collections](../guidelines.md#96-sorting-collections) + - [9.6.1. Interpreting a sorting expression](../guidelines.md#961-interpreting-a-sorting-expression) + - [9.7. Filtering](../guidelines.md#97-filtering) + - [9.7.1. Filter operations](../guidelines.md#971-filter-operations) + - [9.7.2. Operator examples](../guidelines.md#972-operator-examples) + - [9.7.3. Operator precedence](../guidelines.md#973-operator-precedence) + - [9.8. Pagination](../guidelines.md#98-pagination) + - [9.8.1. Server-driven paging](../guidelines.md#981-server-driven-paging) + - [9.8.2. Client-driven paging](../guidelines.md#982-client-driven-paging) + - [9.8.3. Additional considerations](../guidelines.md#983-additional-considerations) + - [9.9. Compound collection operations](../guidelines.md#99-compound-collection-operations) - [**9a. Resource Design**](#9a-resource-design) - [**9a.1. Noun Resources**](#9a1-noun-resources) - [**9a.2. Verb Resources**](#9a2-verb-resources) - [**9a.3. Resource Modeling**](#9a3-resource-modeling) - [10. Delta queries](#10-delta-queries) - - [10.1. Delta links](../guidelines#101-delta-links) - - [10.2. Entity representation](../guidelines#102-entity-representation) - - [10.3. Obtaining a delta link](../guidelines#103-obtaining-a-delta-link) - - [10.4. Contents of a delta link response](../guidelines#104-contents-of-a-delta-link-response) - - [10.5. Using a delta link](../guidelines#105-using-a-delta-link) -- [11. JSON standardizations](../guidelines#11-json-standardizations) - - [11.1. JSON formatting standardization for primitive types](../guidelines#111-json-formatting-standardization-for-primitive-types) - - [11.2. Guidelines for dates and times](../guidelines#112-guidelines-for-dates-and-times) - - [11.2.1. Producing dates](../guidelines#1121-producing-dates) - - [11.2.2. Consuming dates](../guidelines#1122-consuming-dates) - - [11.2.3. Compatibility](../guidelines#1123-compatibility) - - [11.3. JSON serialization of dates and times](../guidelines#113-json-serialization-of-dates-and-times) - - [11.3.1. The `DateLiteral` format](../guidelines#1131-the-dateliteral-format) - - [11.3.2. Commentary on date formatting](../guidelines#1132-commentary-on-date-formatting) - - [11.4. Durations](../guidelines#114-durations) - - [11.5. Intervals](../guidelines#115-intervals) - - [11.6. Repeating intervals](../guidelines#116-repeating-intervals) + - [10.1. Delta links](../guidelines.md#101-delta-links) + - [10.2. Entity representation](../guidelines.md#102-entity-representation) + - [10.3. Obtaining a delta link](../guidelines.md#103-obtaining-a-delta-link) + - [10.4. Contents of a delta link response](../guidelines.md#104-contents-of-a-delta-link-response) + - [10.5. Using a delta link](../guidelines.md#105-using-a-delta-link) +- [11. JSON standardizations](../guidelines.md#11-json-standardizations) + - [11.1. JSON formatting standardization for primitive types](../guidelines.md#111-json-formatting-standardization-for-primitive-types) + - [11.2. Guidelines for dates and times](../guidelines.md#112-guidelines-for-dates-and-times) + - [11.2.1. Producing dates](../guidelines.md#1121-producing-dates) + - [11.2.2. Consuming dates](../guidelines.md#1122-consuming-dates) + - [11.2.3. Compatibility](../guidelines.md#1123-compatibility) + - [11.3. JSON serialization of dates and times](../guidelines.md#113-json-serialization-of-dates-and-times) + - [11.3.1. The `DateLiteral` format](../guidelines.md#1131-the-dateliteral-format) + - [11.3.2. Commentary on date formatting](../guidelines.md#1132-commentary-on-date-formatting) + - [11.4. Durations](../guidelines.md#114-durations) + - [11.5. Intervals](../guidelines.md#115-intervals) + - [11.6. Repeating intervals](../guidelines.md#116-repeating-intervals) - [**11.7. Evolvable Enums**](#117-evolvable-enums) - [**11.8. Dictionary Types**](#118-dictionary-types) - [**11.9. Ommitted Properties**](#119-ommitted-properties) -- [12. Versioning](../guidelines#12-versioning) - - [12.1. Versioning formats](../guidelines#121-versioning-formats) - - [12.1.1. Group versioning](../guidelines#1211-group-versioning) - - [12.2. When to version](../guidelines#122-when-to-version) - - [12.3. Definition of a breaking change](../guidelines#123-definition-of-a-breaking-change) +- [12. Versioning](../guidelines.md#12-versioning) + - [12.1. Versioning formats](../guidelines.md#121-versioning-formats) + - [12.1.1. Group versioning](../guidelines.md#1211-group-versioning) + - [12.2. When to version](../guidelines.md#122-when-to-version) + - [12.3. Definition of a breaking change](../guidelines.md#123-definition-of-a-breaking-change) - [**13. Long running operations**](#13-long-running-operations) - - [**13.1. Resource based long running operations (RELO)**](../long-running-operations#131-resource-based-long-running-operations-relo) - - [**13.2. Stepwise long running operations**](../long-running-operations#132-stepwise-long-running-operations) - - [13.2.1. PUT](../guidelines#1321-put) - - [13.2.2. POST](../guidelines#1322-post) - - [13.2.3. POST, hybrid model](../guidelines#1323-post-hybrid-model) - - [13.2.4. Operations resource](../guidelines#1324-operations-resource) - - [13.2.5. Operation resource](../guidelines#1325-operation-resource) - - [13.2.6. Operation tombstones](../guidelines#1326-operation-tombstones) - - [13.2.7. The typical flow, polling](../guidelines#1327-the-typical-flow-polling) - - [13.2.8. The typical flow, push notifications](../guidelines#1328-the-typical-flow-push-notifications) - - [13.2.9. Retry-After](../guidelines#1329-retry-after) - - [13.3. Retention policy for operation results](../guidelines#133-retention-policy-for-operation-results) -- [14. Throttling, Quotas, and Limits](../guidelines#14-throttling-quotas-and-limits) - - [14.1. Principles](../guidelines#141-principles) - - [14.2. Return Codes (429 vs 503)](../guidelines#142-return-codes-429-vs-503) - - [14.3. Retry-After and RateLimit Headers](../guidelines#143-retry-after-and-ratelimit-headers) - - [14.4. Service Guidance](../guidelines#144-service-guidance) - - [14.4.1. Responsiveness](../guidelines#1441-responsiveness) - - [14.4.2. Rate Limits and Quotas](../guidelines#1442-rate-limits-and-quotas) - - [14.4.3. Overloaded services](../guidelines#1443-overloaded-services) - - [14.4.4. Example Response](../guidelines#1444-example-response) - - [14.5. Caller Guidance](../guidelines#145-caller-guidance) - - [14.6. Handling callers that ignore Retry-After headers](../guidelines#146-handling-callers-that-ignore-retry-after-headers) + - [**13.1. Resource based long running operations (RELO)**](../long-running-operations.md#131-resource-based-long-running-operations-relo) + - [**13.2. Stepwise long running operations**](../long-running-operations.md#132-stepwise-long-running-operations) + - [13.2.1. PUT](../guidelines.md#1321-put) + - [13.2.2. POST](../guidelines.md#1322-post) + - [13.2.3. POST, hybrid model](../guidelines.md#1323-post-hybrid-model) + - [13.2.4. Operations resource](../guidelines.md#1324-operations-resource) + - [13.2.5. Operation resource](../guidelines.md#1325-operation-resource) + - [13.2.6. Operation tombstones](../guidelines.md#1326-operation-tombstones) + - [13.2.7. The typical flow, polling](../guidelines.md#1327-the-typical-flow-polling) + - [13.2.8. The typical flow, push notifications](../guidelines.md#1328-the-typical-flow-push-notifications) + - [13.2.9. Retry-After](../guidelines.md#1329-retry-after) + - [13.3. Retention policy for operation results](../guidelines.md#133-retention-policy-for-operation-results) +- [14. Throttling, Quotas, and Limits](../guidelines.md#14-throttling-quotas-and-limits) + - [14.1. Principles](../guidelines.md#141-principles) + - [14.2. Return Codes (429 vs 503)](../guidelines.md#142-return-codes-429-vs-503) + - [14.3. Retry-After and RateLimit Headers](../guidelines.md#143-retry-after-and-ratelimit-headers) + - [14.4. Service Guidance](../guidelines.md#144-service-guidance) + - [14.4.1. Responsiveness](../guidelines.md#1441-responsiveness) + - [14.4.2. Rate Limits and Quotas](../guidelines.md#1442-rate-limits-and-quotas) + - [14.4.3. Overloaded services](../guidelines.md#1443-overloaded-services) + - [14.4.4. Example Response](../guidelines.md#1444-example-response) + - [14.5. Caller Guidance](../guidelines.md#145-caller-guidance) + - [14.6. Handling callers that ignore Retry-After headers](../guidelines.md#146-handling-callers-that-ignore-retry-after-headers) - [**15. Push notifications via webhooks**](#15-push-notifications-via-webhooks) - - [15.1. Scope](../guidelines#151-scope) - - [15.2. Principles](../guidelines#152-principles) - - [15.3. Types of subscriptions](../guidelines#153-types-of-subscriptions) - - [15.4. Call sequences](../guidelines#154-call-sequences) - - [15.5. Verifying subscriptions](../guidelines#155-verifying-subscriptions) - - [15.6. Receiving notifications](../guidelines#156-receiving-notifications) - - [15.6.1. Notification payload](../guidelines#1561-notification-payload) - - [15.7. Managing subscriptions programmatically](../guidelines#157-managing-subscriptions-programmatically) - - [15.7.1. Creating subscriptions](../guidelines#1571-creating-subscriptions) - - [15.7.2. Updating subscriptions](../guidelines#1572-updating-subscriptions) - - [15.7.3. Deleting subscriptions](../guidelines#1573-deleting-subscriptions) - - [15.7.4. Enumerating subscriptions](../guidelines#1574-enumerating-subscriptions) - - [15.8. Security](../guidelines#158-security) -- [16. Unsupported requests](../guidelines#16-unsupported-requests) - - [16.1. Essential guidance](../guidelines#161-essential-guidance) - - [16.2. Feature allow list](../guidelines#162-feature-allow-list) - - [16.2.1. Error response](../guidelines#1621-error-response) -- [17. Naming guidelines](../guidelines#17-naming-guidelines) - - [17.1. Approach](../guidelines#171-approach) - - [17.2. Casing](../guidelines#172-casing) - - [17.3. Names to avoid](../guidelines#173-names-to-avoid) - - [17.4. Forming compound names](../guidelines#174-forming-compound-names) - - [17.5. Identity properties](../guidelines#175-identity-properties) - - [17.6. Date and time properties](../guidelines#176-date-and-time-properties) - - [17.7. Name properties](../guidelines#177-name-properties) - - [17.8. Collections and counts](../guidelines#178-collections-and-counts) - - [17.9. Common property names](../guidelines#179-common-property-names) + - [15.1. Scope](../guidelines.md#151-scope) + - [15.2. Principles](../guidelines.md#152-principles) + - [15.3. Types of subscriptions](../guidelines.md#153-types-of-subscriptions) + - [15.4. Call sequences](../guidelines.md#154-call-sequences) + - [15.5. Verifying subscriptions](../guidelines.md#155-verifying-subscriptions) + - [15.6. Receiving notifications](../guidelines.md#156-receiving-notifications) + - [15.6.1. Notification payload](../guidelines.md#1561-notification-payload) + - [15.7. Managing subscriptions programmatically](../guidelines.md#157-managing-subscriptions-programmatically) + - [15.7.1. Creating subscriptions](../guidelines.md#1571-creating-subscriptions) + - [15.7.2. Updating subscriptions](../guidelines.md#1572-updating-subscriptions) + - [15.7.3. Deleting subscriptions](../guidelines.md#1573-deleting-subscriptions) + - [15.7.4. Enumerating subscriptions](../guidelines.md#1574-enumerating-subscriptions) + - [15.8. Security](../guidelines.md#158-security) +- [16. Unsupported requests](../guidelines.md#16-unsupported-requests) + - [16.1. Essential guidance](../guidelines.md#161-essential-guidance) + - [16.2. Feature allow list](../guidelines.md#162-feature-allow-list) + - [16.2.1. Error response](../guidelines.md#1621-error-response) +- [17. Naming guidelines](../guidelines.md#17-naming-guidelines) + - [17.1. Approach](../guidelines.md#171-approach) + - [17.2. Casing](../guidelines.md#172-casing) + - [17.3. Names to avoid](../guidelines.md#173-names-to-avoid) + - [17.4. Forming compound names](../guidelines.md#174-forming-compound-names) + - [17.5. Identity properties](../guidelines.md#175-identity-properties) + - [17.6. Date and time properties](../guidelines.md#176-date-and-time-properties) + - [17.7. Name properties](../guidelines.md#177-name-properties) + - [17.8. Collections and counts](../guidelines.md#178-collections-and-counts) + - [17.9. Common property names](../guidelines.md#179-common-property-names) - [**17.10. Type namespaces**](#1710-type-namespaces) -- [18. Appendix](../guidelines#18-appendix) - - [18.1. Sequence diagram notes](../guidelines#181-sequence-diagram-notes) - - [18.1.1. Push notifications, per user flow](../guidelines#1811-push-notifications-per-user-flow) - - [18.1.2. Push notifications, firehose flow](../guidelines#1812-push-notifications-firehose-flow) +- [18. Appendix](../guidelines.md#18-appendix) + - [18.1. Sequence diagram notes](../guidelines.md#181-sequence-diagram-notes) + - [18.1.1. Push notifications, per user flow](../guidelines.md#1811-push-notifications-per-user-flow) + - [18.1.2. Push notifications, firehose flow](../guidelines.md#1812-push-notifications-firehose-flow) - [**18.2. Additional resources**](#182-additional-resources) @@ -187,6 +187,8 @@ The ability to track changes (pull) occuring in the data exposed by Microsoft Gr ### 11.8. Dictionary Types +For scenarios where there is a need to persist a variable number of properties, a [dictionary type](./dictionary/index.md) may be useful. + ### 11.9. Omitting Properties For scenarios where the server contains business logic that determines if a property value should be returned, or not, to the client, a returned representation can be annotated to indicate where properties are [omitted](ommitting-properties). From 6b9b4cd477465edc106958f336ac8c82d69a4320 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Tue, 30 Mar 2021 17:00:34 -0400 Subject: [PATCH 073/729] Fixed reference to Guidelines.md --- graph/guidelines.md | 270 ++++++++++++++++++++++---------------------- 1 file changed, 135 insertions(+), 135 deletions(-) diff --git a/graph/guidelines.md b/graph/guidelines.md index 828bab4a..32c27230 100644 --- a/graph/guidelines.md +++ b/graph/guidelines.md @@ -9,154 +9,154 @@ The following table of contents links back to the primary guidelines where there ## 2. Table of contents -- [Microsoft REST API Guidelines Working Group](../guidelines.md#microsoft-rest-api-guidelines-working-group) -- [1. Abstract](../guidelines.md#1-abstract) -- [2. Table of contents](../guidelines.md#2-table-of-contents) -- [3. Introduction](../guidelines.md#3-introduction) - - [3.1. Recommended reading](../guidelines.md#31-recommended-reading) -- [4. Interpreting the guidelines](../guidelines.md#4-interpreting-the-guidelines) - - [4.1. Application of the guidelines](../guidelines.md#41-application-of-the-guidelines) - - [4.2. Guidelines for existing services and versioning of services](../guidelines.md#42-guidelines-for-existing-services-and-versioning-of-services) - - [4.3. Requirements language](../guidelines.md#43-requirements-language) - - [4.4. License](../guidelines.md#44-license) -- [5. Taxonomy](../guidelines.md#5-taxonomy) - - [5.1. Errors](../guidelines.md#51-errors) - - [5.2. Faults](../guidelines.md#52-faults) - - [5.3. Latency](../guidelines.md#53-latency) - - [5.4. Time to complete](../guidelines.md#54-time-to-complete) - - [5.5. Long running API faults](../guidelines.md#55-long-running-api-faults) -- [6. Client guidance](../guidelines.md#6-client-guidance) - - [6.1. Ignore rule](../guidelines.md#61-ignore-rule) - - [6.2. Variable order rule](../guidelines.md#62-variable-order-rule) - - [6.3. Silent fail rule](../guidelines.md#63-silent-fail-rule) -- [7. Consistency fundamentals](../guidelines.md#7-consistency-fundamentals) - - [7.1. URL structure](../guidelines.md#71-url-structure) - - [7.2. URL length](../guidelines.md#72-url-length) - - [7.3. Canonical identifier](../guidelines.md#73-canonical-identifier) - - [7.4. Supported methods](../guidelines.md#74-supported-methods) - - [7.4.1. POST](../guidelines.md#741-post) - - [7.4.2. PATCH](../guidelines.md#742-patch) - - [7.4.3. Creating resources via PATCH (UPSERT semantics)](../guidelines.md#743-creating-resources-via-patch-upsert-semantics) - - [7.4.4. Options and link headers](../guidelines.md#744-options-and-link-headers) - - [7.5. Standard request headers](../guidelines.md#75-standard-request-headers) - - [7.6. Standard response headers](../guidelines.md#76-standard-response-headers) - - [7.7. Custom headers](../guidelines.md#77-custom-headers) - - [7.8. Specifying headers as query parameters](../guidelines.md#78-specifying-headers-as-query-parameters) - - [7.9. PII parameters](../guidelines.md#79-pii-parameters) - - [7.10. Response formats](../guidelines.md#710-response-formats) - - [7.10.1. Clients-specified response format](../guidelines.md#7101-clients-specified-response-format) - - [7.10.2. Error condition responses](../guidelines.md#7102-error-condition-responses) - - [7.11. HTTP Status Codes](../guidelines.md#711-http-status-codes) - - [7.12. Client library optional](../guidelines.md#712-client-library-optional) -- [8. CORS](../guidelines.md#8-cors) - - [8.1. Client guidance](../guidelines.md#81-client-guidance) - - [8.1.1. Avoiding preflight](../guidelines.md#811-avoiding-preflight) - - [8.2. Service guidance](../guidelines.md#82-service-guidance) -- [9. Collections](../guidelines.md#9-collections) - - [9.1. Item keys](../guidelines.md#91-item-keys) - - [9.2. Serialization](../guidelines.md#92-serialization) - - [9.3. Collection URL patterns](../guidelines.md#93-collection-url-patterns) - - [9.3.1. Nested collections and properties](../guidelines.md#931-nested-collections-and-properties) - - [9.4. Big collections](../guidelines.md#94-big-collections) - - [9.5. Changing collections](../guidelines.md#95-changing-collections) - - [9.6. Sorting collections](../guidelines.md#96-sorting-collections) - - [9.6.1. Interpreting a sorting expression](../guidelines.md#961-interpreting-a-sorting-expression) - - [9.7. Filtering](../guidelines.md#97-filtering) - - [9.7.1. Filter operations](../guidelines.md#971-filter-operations) - - [9.7.2. Operator examples](../guidelines.md#972-operator-examples) - - [9.7.3. Operator precedence](../guidelines.md#973-operator-precedence) - - [9.8. Pagination](../guidelines.md#98-pagination) - - [9.8.1. Server-driven paging](../guidelines.md#981-server-driven-paging) - - [9.8.2. Client-driven paging](../guidelines.md#982-client-driven-paging) - - [9.8.3. Additional considerations](../guidelines.md#983-additional-considerations) - - [9.9. Compound collection operations](../guidelines.md#99-compound-collection-operations) +- [Microsoft REST API Guidelines Working Group](../Guidelines.md#microsoft-rest-api-guidelines-working-group) +- [1. Abstract](../Guidelines.md#1-abstract) +- [2. Table of contents](../Guidelines.md#2-table-of-contents) +- [3. Introduction](../Guidelines.md#3-introduction) + - [3.1. Recommended reading](../Guidelines.md#31-recommended-reading) +- [4. Interpreting the guidelines](../Guidelines.md#4-interpreting-the-guidelines) + - [4.1. Application of the guidelines](../Guidelines.md#41-application-of-the-guidelines) + - [4.2. Guidelines for existing services and versioning of services](../Guidelines.md#42-guidelines-for-existing-services-and-versioning-of-services) + - [4.3. Requirements language](../Guidelines.md#43-requirements-language) + - [4.4. License](../Guidelines.md#44-license) +- [5. Taxonomy](../Guidelines.md#5-taxonomy) + - [5.1. Errors](../Guidelines.md#51-errors) + - [5.2. Faults](../Guidelines.md#52-faults) + - [5.3. Latency](../Guidelines.md#53-latency) + - [5.4. Time to complete](../Guidelines.md#54-time-to-complete) + - [5.5. Long running API faults](../Guidelines.md#55-long-running-api-faults) +- [6. Client guidance](../Guidelines.md#6-client-guidance) + - [6.1. Ignore rule](../Guidelines.md#61-ignore-rule) + - [6.2. Variable order rule](../Guidelines.md#62-variable-order-rule) + - [6.3. Silent fail rule](../Guidelines.md#63-silent-fail-rule) +- [7. Consistency fundamentals](../Guidelines.md#7-consistency-fundamentals) + - [7.1. URL structure](../Guidelines.md#71-url-structure) + - [7.2. URL length](../Guidelines.md#72-url-length) + - [7.3. Canonical identifier](../Guidelines.md#73-canonical-identifier) + - [7.4. Supported methods](../Guidelines.md#74-supported-methods) + - [7.4.1. POST](../Guidelines.md#741-post) + - [7.4.2. PATCH](../Guidelines.md#742-patch) + - [7.4.3. Creating resources via PATCH (UPSERT semantics)](../Guidelines.md#743-creating-resources-via-patch-upsert-semantics) + - [7.4.4. Options and link headers](../Guidelines.md#744-options-and-link-headers) + - [7.5. Standard request headers](../Guidelines.md#75-standard-request-headers) + - [7.6. Standard response headers](../Guidelines.md#76-standard-response-headers) + - [7.7. Custom headers](../Guidelines.md#77-custom-headers) + - [7.8. Specifying headers as query parameters](../Guidelines.md#78-specifying-headers-as-query-parameters) + - [7.9. PII parameters](../Guidelines.md#79-pii-parameters) + - [7.10. Response formats](../Guidelines.md#710-response-formats) + - [7.10.1. Clients-specified response format](../Guidelines.md#7101-clients-specified-response-format) + - [7.10.2. Error condition responses](../Guidelines.md#7102-error-condition-responses) + - [7.11. HTTP Status Codes](../Guidelines.md#711-http-status-codes) + - [7.12. Client library optional](../Guidelines.md#712-client-library-optional) +- [8. CORS](../Guidelines.md#8-cors) + - [8.1. Client guidance](../Guidelines.md#81-client-guidance) + - [8.1.1. Avoiding preflight](../Guidelines.md#811-avoiding-preflight) + - [8.2. Service guidance](../Guidelines.md#82-service-guidance) +- [9. Collections](../Guidelines.md#9-collections) + - [9.1. Item keys](../Guidelines.md#91-item-keys) + - [9.2. Serialization](../Guidelines.md#92-serialization) + - [9.3. Collection URL patterns](../Guidelines.md#93-collection-url-patterns) + - [9.3.1. Nested collections and properties](../Guidelines.md#931-nested-collections-and-properties) + - [9.4. Big collections](../Guidelines.md#94-big-collections) + - [9.5. Changing collections](../Guidelines.md#95-changing-collections) + - [9.6. Sorting collections](../Guidelines.md#96-sorting-collections) + - [9.6.1. Interpreting a sorting expression](../Guidelines.md#961-interpreting-a-sorting-expression) + - [9.7. Filtering](../Guidelines.md#97-filtering) + - [9.7.1. Filter operations](../Guidelines.md#971-filter-operations) + - [9.7.2. Operator examples](../Guidelines.md#972-operator-examples) + - [9.7.3. Operator precedence](../Guidelines.md#973-operator-precedence) + - [9.8. Pagination](../Guidelines.md#98-pagination) + - [9.8.1. Server-driven paging](../Guidelines.md#981-server-driven-paging) + - [9.8.2. Client-driven paging](../Guidelines.md#982-client-driven-paging) + - [9.8.3. Additional considerations](../Guidelines.md#983-additional-considerations) + - [9.9. Compound collection operations](../Guidelines.md#99-compound-collection-operations) - [**9a. Resource Design**](#9a-resource-design) - [**9a.1. Noun Resources**](#9a1-noun-resources) - [**9a.2. Verb Resources**](#9a2-verb-resources) - [**9a.3. Resource Modeling**](#9a3-resource-modeling) - [10. Delta queries](#10-delta-queries) - - [10.1. Delta links](../guidelines.md#101-delta-links) - - [10.2. Entity representation](../guidelines.md#102-entity-representation) - - [10.3. Obtaining a delta link](../guidelines.md#103-obtaining-a-delta-link) - - [10.4. Contents of a delta link response](../guidelines.md#104-contents-of-a-delta-link-response) - - [10.5. Using a delta link](../guidelines.md#105-using-a-delta-link) -- [11. JSON standardizations](../guidelines.md#11-json-standardizations) - - [11.1. JSON formatting standardization for primitive types](../guidelines.md#111-json-formatting-standardization-for-primitive-types) - - [11.2. Guidelines for dates and times](../guidelines.md#112-guidelines-for-dates-and-times) - - [11.2.1. Producing dates](../guidelines.md#1121-producing-dates) - - [11.2.2. Consuming dates](../guidelines.md#1122-consuming-dates) - - [11.2.3. Compatibility](../guidelines.md#1123-compatibility) - - [11.3. JSON serialization of dates and times](../guidelines.md#113-json-serialization-of-dates-and-times) - - [11.3.1. The `DateLiteral` format](../guidelines.md#1131-the-dateliteral-format) - - [11.3.2. Commentary on date formatting](../guidelines.md#1132-commentary-on-date-formatting) - - [11.4. Durations](../guidelines.md#114-durations) - - [11.5. Intervals](../guidelines.md#115-intervals) - - [11.6. Repeating intervals](../guidelines.md#116-repeating-intervals) + - [10.1. Delta links](../Guidelines.md#101-delta-links) + - [10.2. Entity representation](../Guidelines.md#102-entity-representation) + - [10.3. Obtaining a delta link](../Guidelines.md#103-obtaining-a-delta-link) + - [10.4. Contents of a delta link response](../Guidelines.md#104-contents-of-a-delta-link-response) + - [10.5. Using a delta link](../Guidelines.md#105-using-a-delta-link) +- [11. JSON standardizations](../Guidelines.md#11-json-standardizations) + - [11.1. JSON formatting standardization for primitive types](../Guidelines.md#111-json-formatting-standardization-for-primitive-types) + - [11.2. Guidelines for dates and times](../Guidelines.md#112-guidelines-for-dates-and-times) + - [11.2.1. Producing dates](../Guidelines.md#1121-producing-dates) + - [11.2.2. Consuming dates](../Guidelines.md#1122-consuming-dates) + - [11.2.3. Compatibility](../Guidelines.md#1123-compatibility) + - [11.3. JSON serialization of dates and times](../Guidelines.md#113-json-serialization-of-dates-and-times) + - [11.3.1. The `DateLiteral` format](../Guidelines.md#1131-the-dateliteral-format) + - [11.3.2. Commentary on date formatting](../Guidelines.md#1132-commentary-on-date-formatting) + - [11.4. Durations](../Guidelines.md#114-durations) + - [11.5. Intervals](../Guidelines.md#115-intervals) + - [11.6. Repeating intervals](../Guidelines.md#116-repeating-intervals) - [**11.7. Evolvable Enums**](#117-evolvable-enums) - [**11.8. Dictionary Types**](#118-dictionary-types) - [**11.9. Ommitted Properties**](#119-ommitted-properties) -- [12. Versioning](../guidelines.md#12-versioning) - - [12.1. Versioning formats](../guidelines.md#121-versioning-formats) - - [12.1.1. Group versioning](../guidelines.md#1211-group-versioning) - - [12.2. When to version](../guidelines.md#122-when-to-version) - - [12.3. Definition of a breaking change](../guidelines.md#123-definition-of-a-breaking-change) +- [12. Versioning](../Guidelines.md#12-versioning) + - [12.1. Versioning formats](../Guidelines.md#121-versioning-formats) + - [12.1.1. Group versioning](../Guidelines.md#1211-group-versioning) + - [12.2. When to version](../Guidelines.md#122-when-to-version) + - [12.3. Definition of a breaking change](../Guidelines.md#123-definition-of-a-breaking-change) - [**13. Long running operations**](#13-long-running-operations) - [**13.1. Resource based long running operations (RELO)**](../long-running-operations.md#131-resource-based-long-running-operations-relo) - [**13.2. Stepwise long running operations**](../long-running-operations.md#132-stepwise-long-running-operations) - - [13.2.1. PUT](../guidelines.md#1321-put) - - [13.2.2. POST](../guidelines.md#1322-post) - - [13.2.3. POST, hybrid model](../guidelines.md#1323-post-hybrid-model) - - [13.2.4. Operations resource](../guidelines.md#1324-operations-resource) - - [13.2.5. Operation resource](../guidelines.md#1325-operation-resource) - - [13.2.6. Operation tombstones](../guidelines.md#1326-operation-tombstones) - - [13.2.7. The typical flow, polling](../guidelines.md#1327-the-typical-flow-polling) - - [13.2.8. The typical flow, push notifications](../guidelines.md#1328-the-typical-flow-push-notifications) - - [13.2.9. Retry-After](../guidelines.md#1329-retry-after) - - [13.3. Retention policy for operation results](../guidelines.md#133-retention-policy-for-operation-results) -- [14. Throttling, Quotas, and Limits](../guidelines.md#14-throttling-quotas-and-limits) - - [14.1. Principles](../guidelines.md#141-principles) - - [14.2. Return Codes (429 vs 503)](../guidelines.md#142-return-codes-429-vs-503) - - [14.3. Retry-After and RateLimit Headers](../guidelines.md#143-retry-after-and-ratelimit-headers) - - [14.4. Service Guidance](../guidelines.md#144-service-guidance) - - [14.4.1. Responsiveness](../guidelines.md#1441-responsiveness) - - [14.4.2. Rate Limits and Quotas](../guidelines.md#1442-rate-limits-and-quotas) - - [14.4.3. Overloaded services](../guidelines.md#1443-overloaded-services) - - [14.4.4. Example Response](../guidelines.md#1444-example-response) - - [14.5. Caller Guidance](../guidelines.md#145-caller-guidance) - - [14.6. Handling callers that ignore Retry-After headers](../guidelines.md#146-handling-callers-that-ignore-retry-after-headers) + - [13.2.1. PUT](../Guidelines.md#1321-put) + - [13.2.2. POST](../Guidelines.md#1322-post) + - [13.2.3. POST, hybrid model](../Guidelines.md#1323-post-hybrid-model) + - [13.2.4. Operations resource](../Guidelines.md#1324-operations-resource) + - [13.2.5. Operation resource](../Guidelines.md#1325-operation-resource) + - [13.2.6. Operation tombstones](../Guidelines.md#1326-operation-tombstones) + - [13.2.7. The typical flow, polling](../Guidelines.md#1327-the-typical-flow-polling) + - [13.2.8. The typical flow, push notifications](../Guidelines.md#1328-the-typical-flow-push-notifications) + - [13.2.9. Retry-After](../Guidelines.md#1329-retry-after) + - [13.3. Retention policy for operation results](../Guidelines.md#133-retention-policy-for-operation-results) +- [14. Throttling, Quotas, and Limits](../Guidelines.md#14-throttling-quotas-and-limits) + - [14.1. Principles](../Guidelines.md#141-principles) + - [14.2. Return Codes (429 vs 503)](../Guidelines.md#142-return-codes-429-vs-503) + - [14.3. Retry-After and RateLimit Headers](../Guidelines.md#143-retry-after-and-ratelimit-headers) + - [14.4. Service Guidance](../Guidelines.md#144-service-guidance) + - [14.4.1. Responsiveness](../Guidelines.md#1441-responsiveness) + - [14.4.2. Rate Limits and Quotas](../Guidelines.md#1442-rate-limits-and-quotas) + - [14.4.3. Overloaded services](../Guidelines.md#1443-overloaded-services) + - [14.4.4. Example Response](../Guidelines.md#1444-example-response) + - [14.5. Caller Guidance](../Guidelines.md#145-caller-guidance) + - [14.6. Handling callers that ignore Retry-After headers](../Guidelines.md#146-handling-callers-that-ignore-retry-after-headers) - [**15. Push notifications via webhooks**](#15-push-notifications-via-webhooks) - - [15.1. Scope](../guidelines.md#151-scope) - - [15.2. Principles](../guidelines.md#152-principles) - - [15.3. Types of subscriptions](../guidelines.md#153-types-of-subscriptions) - - [15.4. Call sequences](../guidelines.md#154-call-sequences) - - [15.5. Verifying subscriptions](../guidelines.md#155-verifying-subscriptions) - - [15.6. Receiving notifications](../guidelines.md#156-receiving-notifications) - - [15.6.1. Notification payload](../guidelines.md#1561-notification-payload) - - [15.7. Managing subscriptions programmatically](../guidelines.md#157-managing-subscriptions-programmatically) - - [15.7.1. Creating subscriptions](../guidelines.md#1571-creating-subscriptions) - - [15.7.2. Updating subscriptions](../guidelines.md#1572-updating-subscriptions) - - [15.7.3. Deleting subscriptions](../guidelines.md#1573-deleting-subscriptions) - - [15.7.4. Enumerating subscriptions](../guidelines.md#1574-enumerating-subscriptions) - - [15.8. Security](../guidelines.md#158-security) -- [16. Unsupported requests](../guidelines.md#16-unsupported-requests) - - [16.1. Essential guidance](../guidelines.md#161-essential-guidance) - - [16.2. Feature allow list](../guidelines.md#162-feature-allow-list) - - [16.2.1. Error response](../guidelines.md#1621-error-response) -- [17. Naming guidelines](../guidelines.md#17-naming-guidelines) - - [17.1. Approach](../guidelines.md#171-approach) - - [17.2. Casing](../guidelines.md#172-casing) - - [17.3. Names to avoid](../guidelines.md#173-names-to-avoid) - - [17.4. Forming compound names](../guidelines.md#174-forming-compound-names) - - [17.5. Identity properties](../guidelines.md#175-identity-properties) - - [17.6. Date and time properties](../guidelines.md#176-date-and-time-properties) - - [17.7. Name properties](../guidelines.md#177-name-properties) - - [17.8. Collections and counts](../guidelines.md#178-collections-and-counts) - - [17.9. Common property names](../guidelines.md#179-common-property-names) + - [15.1. Scope](../Guidelines.md#151-scope) + - [15.2. Principles](../Guidelines.md#152-principles) + - [15.3. Types of subscriptions](../Guidelines.md#153-types-of-subscriptions) + - [15.4. Call sequences](../Guidelines.md#154-call-sequences) + - [15.5. Verifying subscriptions](../Guidelines.md#155-verifying-subscriptions) + - [15.6. Receiving notifications](../Guidelines.md#156-receiving-notifications) + - [15.6.1. Notification payload](../Guidelines.md#1561-notification-payload) + - [15.7. Managing subscriptions programmatically](../Guidelines.md#157-managing-subscriptions-programmatically) + - [15.7.1. Creating subscriptions](../Guidelines.md#1571-creating-subscriptions) + - [15.7.2. Updating subscriptions](../Guidelines.md#1572-updating-subscriptions) + - [15.7.3. Deleting subscriptions](../Guidelines.md#1573-deleting-subscriptions) + - [15.7.4. Enumerating subscriptions](../Guidelines.md#1574-enumerating-subscriptions) + - [15.8. Security](../Guidelines.md#158-security) +- [16. Unsupported requests](../Guidelines.md#16-unsupported-requests) + - [16.1. Essential guidance](../Guidelines.md#161-essential-guidance) + - [16.2. Feature allow list](../Guidelines.md#162-feature-allow-list) + - [16.2.1. Error response](../Guidelines.md#1621-error-response) +- [17. Naming guidelines](../Guidelines.md#17-naming-guidelines) + - [17.1. Approach](../Guidelines.md#171-approach) + - [17.2. Casing](../Guidelines.md#172-casing) + - [17.3. Names to avoid](../Guidelines.md#173-names-to-avoid) + - [17.4. Forming compound names](../Guidelines.md#174-forming-compound-names) + - [17.5. Identity properties](../Guidelines.md#175-identity-properties) + - [17.6. Date and time properties](../Guidelines.md#176-date-and-time-properties) + - [17.7. Name properties](../Guidelines.md#177-name-properties) + - [17.8. Collections and counts](../Guidelines.md#178-collections-and-counts) + - [17.9. Common property names](../Guidelines.md#179-common-property-names) - [**17.10. Type namespaces**](#1710-type-namespaces) -- [18. Appendix](../guidelines.md#18-appendix) - - [18.1. Sequence diagram notes](../guidelines.md#181-sequence-diagram-notes) - - [18.1.1. Push notifications, per user flow](../guidelines.md#1811-push-notifications-per-user-flow) - - [18.1.2. Push notifications, firehose flow](../guidelines.md#1812-push-notifications-firehose-flow) +- [18. Appendix](../Guidelines.md#18-appendix) + - [18.1. Sequence diagram notes](../Guidelines.md#181-sequence-diagram-notes) + - [18.1.1. Push notifications, per user flow](../Guidelines.md#1811-push-notifications-per-user-flow) + - [18.1.2. Push notifications, firehose flow](../Guidelines.md#1812-push-notifications-firehose-flow) - [**18.2. Additional resources**](#182-additional-resources) From 790504f48ed1c96bae1fca569d18ed1d33f4b68b Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Mon, 10 May 2021 19:02:17 -0700 Subject: [PATCH 074/729] Added guidance for repeatable requests --- azure/Guidelines.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 7b79d817..273aaac3 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -296,6 +296,16 @@ Clients that use version discovery are expected to cache version information. Si The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. +## Repeatability of requests + +The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. + +A service **SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). + +- The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. +- A service advertises support for repeatability requests by adding the `Repeatbility-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. +- When understood, all endpoints co-located behind a DNS name **MUST** understand the header. This means that a service **MUST NOT** ignore the presence of a header for any endpoints behind the DNS name, but rather fail the request containing a `Repeatability-Request-ID` header if that particular endpoint lacks support for repeatable requests. Such partial support **SHOULD** be avoided due to the confusion it causes for clients. + ## Retiring pre-release and beta APIs Pre-release and beta APIs are not covered by the Azure Global Retirement and Deprecation Policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. From 58f621866ef30d52ff63c5cfdf00b21940bae1e4 Mon Sep 17 00:00:00 2001 From: bl-ue Date: Wed, 16 Jun 2021 15:33:08 -0400 Subject: [PATCH 075/729] Fix typos --- azure/Guidelines.md | 2 +- graph/dictionary/client-guidance.md | 2 +- graph/dictionary/index.md | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 7b79d817..7ceabb9f 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -172,7 +172,7 @@ Evolutionary changes do not require prior approval (but still need a version bum #### Changing the API without changing the version -Because the API version represents a contract that a developer can rely on when generating SDKs to communicate with the service, there are a limited set of situations where changing the API is permissable without a version bump. The only changes universally allowed: +Because the API version represents a contract that a developer can rely on when generating SDKs to communicate with the service, there are a limited set of situations where changing the API is permissible without a version bump. The only changes universally allowed: 1. Adding a new (optional) value to an extensible enum. diff --git a/graph/dictionary/client-guidance.md b/graph/dictionary/client-guidance.md index fd7b2c20..a048e6c5 100644 --- a/graph/dictionary/client-guidance.md +++ b/graph/dictionary/client-guidance.md @@ -42,7 +42,7 @@ SDKs need to provide support for dictionary types so SDK consummers get a deligh - Dictionaries support OData annotations (values prefixed with **@OData**), such annotations should not be inserted directly in the dictionary but rather in the additional properties manager. - Dictionary types can inherit another dictionary type, this inheritance must be respected. -- Dictionary values can be of union types, if the target langauge doesn't support union types, a wrapper type should be generated as backward compatible solution with properties for each type of the union. +- Dictionary values can be of union types, if the target language doesn't support union types, a wrapper type should be generated as backward compatible solution with properties for each type of the union. ### Dotnet diff --git a/graph/dictionary/index.md b/graph/dictionary/index.md index fb2fdce7..d9ba2874 100644 --- a/graph/dictionary/index.md +++ b/graph/dictionary/index.md @@ -22,7 +22,7 @@ Before using a dictionary type in your API definition make sure your scenario fi ## JSON payload example -The following example illustrates the resulting JSON for a property of dictionary type. The parent object has been ommitted for brievety. +The following example illustrates the resulting JSON for a property of dictionary type. The parent object has been omitted for brievety. ```json { @@ -48,7 +48,7 @@ In this set of examples we're modeling a **roles** property of dictionary type o GET https://graph.microsoft.com/v1.0/users/10/roles/author ``` -Reponse: +Response: ```json { @@ -62,7 +62,7 @@ Reponse: GET https://graph.microsoft.com/v1.0/users/10/roles ``` -Reponse: +Response: ```json { @@ -84,7 +84,7 @@ Reponse: GET https://graph.microsoft.com/v1.0/users/10 ``` -Reponse: +Response: ```json { @@ -134,7 +134,7 @@ PATCH https://graph.microsoft.com/v1.0/users/10/roles ``` > Note: setting one of the keys to **null** deletes it from the dictionary. -> Note: the domain values for the existing author and maintainer entries will get udpated. +> Note: the domain values for the existing author and maintainer entries will get updated. > Note: the reviewer entry will be inserted in the dictionary. ### Updating an entry in the dictionary From 9c708206b659a20e140704640302c933cec96c75 Mon Sep 17 00:00:00 2001 From: bl-ue Date: Wed, 16 Jun 2021 15:38:45 -0400 Subject: [PATCH 076/729] Fix a few more typos --- azure/Guidelines.md | 14 +++++++------- graph/dictionary/client-guidance.md | 18 +++++++++--------- graph/dictionary/index.md | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 7ceabb9f..92b2cd67 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -26,7 +26,7 @@ Developing a new service requires the development of at least 1 (management plan > A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. -* Think about naming from the context of a **developer experience**. +* Think about naming from the context of a **developer experience**. * Start with the "things" your API manipulates, then think about the operations that a developer needs to do to these "things". * Keep the verbs present-tense. Avoid the use of past or future tense in most cases. * Avoid the use of generic names like "Object", "Job", "Task", "Operation" (for example - the list is not exhaustive). @@ -34,7 +34,7 @@ Developing a new service requires the development of at least 1 (management plan * Think about the code that a customer will write both before and after the REST API call. How will a developer use this API in the canonical use case? * Consider multiple languages, and include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). * Use previews to get the shape right. There are different notification, breaking change, and lifetime requirements on preview API versions. - * We recommend a minimum of 2 preview versions prior to your first GA release. However, there is no hard rule for previews. + * We recommend a minimum of 2 preview versions prior to your first GA release. However, there is no hard rule for previews. * You should gather feedback from your customers and iterate until the API is useful. Actively solicit feedback from your preview customers. Preventing future breaking changes is a source of concern during initial review. Without a history, the review process attempts to identify patterns that may result in breaking changes later on. For instance, think about the following: @@ -54,7 +54,7 @@ Preventing future breaking changes is a source of concern during initial review. * Implement managed identity access controls for accessing the other resource. Do not accept connection strings as a method of specifying access permissions. * Be concerned about data widths of numeric types. Wider data types (e.g. 64-bit vs. 32-bit) are more future-proof. * Think about how the interface will be represented by an SDK. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. -* Implement (and encourage) the use of PATCH for resource modifications. +* Implement (and encourage) the use of PATCH for resource modifications. * The PATCH operation should be able to modify any mutable property on the resource. * Prefer JSON merge-patch ([RFC 7396](https://tools.ietf.org/html/rfc7396)) over JSON patch ([RFC 6902](https://tools.ietf.org/html/rfc6902)) as the accepted data format for PATCH operations. * Get a security review of your API. You should be especially concerned with PII leakage, GDPR compliance and any other compliance regulations appropriate to your situation. @@ -191,7 +191,7 @@ This functionality is only available for single cloud deployments because the AP Do not add computed output values if the computed value can be calculated from other information in the payload. It unnecessarily expands the payload. -All situations where the API definition is changed (irrespective of whether a version change happens or not) **MUST** be reviewed by the Azure REST API Review Board before release. +All situations where the API definition is changed (irrespective of whether a version change happens or not) **MUST** be reviewed by the Azure REST API Review Board before release. ### Preview Versions @@ -208,7 +208,7 @@ Preview versions are not treated the same way as release versions. In general, You should follow the axiom "don't surprise your customers" when deciding whether to increment the preview version, and err on the side of incrementing the preview version. -Preview APIs [follow a different path for deprecation and retirement](https://dev.azure.com/msazure/AzureWiki/_wiki/wikis/AzureWiki.wiki/37683/Retirement-of-Previews), and are not subject to the same guarantees as APIs marked as generally available. Preview APIs have a life-span of not more than 12 months, after which they must be retired. +Preview APIs [follow a different path for deprecation and retirement](https://dev.azure.com/msazure/AzureWiki/_wiki/wikis/AzureWiki.wiki/37683/Retirement-of-Previews), and are not subject to the same guarantees as APIs marked as generally available. Preview APIs have a life-span of not more than 12 months, after which they must be retired. #### Why Azure recommends conservative API versioning @@ -220,7 +220,7 @@ There are a few mechanisms that can reduce breaking changes and their effects on #### Use PATCH instead of PUT for updates -The HTTP PUT verb is an idemopotent apply operation for the API version being used. The [Microsoft API Guidelines already recommend the use of PATCH](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#742-patch) for updates. +The HTTP PUT verb is an idempotent apply operation for the API version being used. The [Microsoft API Guidelines already recommend the use of PATCH](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#742-patch) for updates. Consider the following sequence: @@ -252,7 +252,7 @@ While removing a value from an enum is a breaking change, adding an enum can be } ``` -Always model an enum as a string unleess you are positive that the symbol set will **NEVER** change over time. +Always model an enum as a string unless you are positive that the symbol set will **NEVER** change over time. ### Group versioning in Azure and Azure Stack diff --git a/graph/dictionary/client-guidance.md b/graph/dictionary/client-guidance.md index a048e6c5..58829e47 100644 --- a/graph/dictionary/client-guidance.md +++ b/graph/dictionary/client-guidance.md @@ -13,12 +13,12 @@ In **components** in **schemas**: ```json { "roleSettings": { - "type": "object", + "type": "object", "properties": { - "domain": { - "type": "string" - } - } + "domain": { + "type": "string" + } + } } } } @@ -28,7 +28,7 @@ In **components** in **schemas**: { "type": "object", "patternProperties": { - ".*": { + ".*": { "$ref": "#/components/schemas/roleSettings" }, "additionalProperties": false @@ -38,7 +38,7 @@ In **components** in **schemas**: ## SDK Support -SDKs need to provide support for dictionary types so SDK consummers get a delightful development experience. Examples are provided below for different languages. Other aspects need to be taken into considerations: +SDKs need to provide support for dictionary types so SDK consumers get a delightful development experience. Examples are provided below for different languages. Other aspects need to be taken into considerations: - Dictionaries support OData annotations (values prefixed with **@OData**), such annotations should not be inserted directly in the dictionary but rather in the additional properties manager. - Dictionary types can inherit another dictionary type, this inheritance must be respected. @@ -72,6 +72,6 @@ or ## Request builder generation annotation -By default SDKs are not required to contain a set of request builders to run CRUD requests on entries in the dictionary. The dictionary will be updated as a whole by consummers by sending requests to the parent entity. +By default SDKs are not required to contain a set of request builders to run CRUD requests on entries in the dictionary. The dictionary will be updated as a whole by consumers by sending requests to the parent entity. -If a **SupportedHttpMethod** annotation is specified for the dictionary type, request builders should be generated to allow consummers to automically update the entries. +If a **SupportedHttpMethod** annotation is specified for the dictionary type, request builders should be generated to allow consumers to automatically update the entries. diff --git a/graph/dictionary/index.md b/graph/dictionary/index.md index d9ba2874..dd3dfa24 100644 --- a/graph/dictionary/index.md +++ b/graph/dictionary/index.md @@ -22,7 +22,7 @@ Before using a dictionary type in your API definition make sure your scenario fi ## JSON payload example -The following example illustrates the resulting JSON for a property of dictionary type. The parent object has been omitted for brievety. +The following example illustrates the resulting JSON for a property of dictionary type. The parent object has been omitted for brevity. ```json { @@ -172,7 +172,7 @@ The following example defines a complex type **roleSettings** as well as a dicti - + GET PATCH DELETE From 3bbafb230b0a96ff696024c19cdae110d30dbb32 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Thu, 24 Jun 2021 13:51:33 -0700 Subject: [PATCH 077/729] Update azure/Guidelines.md LGTM Co-authored-by: Mike Kistler --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 273aaac3..30103a53 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -300,7 +300,7 @@ The Microsoft REST API guidelines for Long Running Operations are an updated, cl The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. -A service **SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). +A service **SHOULD** support repeatable requests as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). - The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. - A service advertises support for repeatability requests by adding the `Repeatbility-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. From 2a479c6711d65e535dd59533afadb7dcb95c3395 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Thu, 24 Jun 2021 13:52:11 -0700 Subject: [PATCH 078/729] Update azure/Guidelines.md +1. LGTM. Co-authored-by: Mike Kistler --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 30103a53..4609240c 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -304,7 +304,7 @@ A service **SHOULD** support repeatable requests as defined in [OASIS Repeatable - The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. - A service advertises support for repeatability requests by adding the `Repeatbility-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. -- When understood, all endpoints co-located behind a DNS name **MUST** understand the header. This means that a service **MUST NOT** ignore the presence of a header for any endpoints behind the DNS name, but rather fail the request containing a `Repeatability-Request-ID` header if that particular endpoint lacks support for repeatable requests. Such partial support **SHOULD** be avoided due to the confusion it causes for clients. +- When supported, all endpoints co-located behind a DNS name **MUST** support the header. This means that a service **MUST NOT** ignore the presence of a header for any endpoints behind the DNS name, but rather fail the request containing a `Repeatability-Request-ID` header if that particular endpoint lacks support for repeatable requests. Such partial support **SHOULD** be avoided due to the confusion it causes for clients. ## Retiring pre-release and beta APIs From dcc14df2457fdf19cefba24c4a4ae619cd43291b Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 2 Jul 2021 17:24:41 -0400 Subject: [PATCH 079/729] Cleaned to prep for new Guidelines review. --- azure/Guidelines.md | 328 -------------------------------------------- 1 file changed, 328 deletions(-) delete mode 100644 azure/Guidelines.md diff --git a/azure/Guidelines.md b/azure/Guidelines.md deleted file mode 100644 index ea8aa6d9..00000000 --- a/azure/Guidelines.md +++ /dev/null @@ -1,328 +0,0 @@ -# Microsoft Azure REST API Guidelines - -## History - -| Date | Version | Notes | -| ----------- | ------- | --------------------------------------------------- | -| 2020-Mar-31 | v3.1 | 1st public release of the Azure REST API Guidelines | -| 2020-Jul-31 | v3.2 | Added service advice for initial versions | - -## Introduction - -The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. - -### Additional guidance for Azure Resource Manager resource providers - -Teams building ARM Resource Providers (RPs) MUST follow the additional guidance in the ARM Resource Provider Contract (RPC) and related documents. These documents can be found here. - -* [Azure Resource Manager Wiki][2] (Internal only) -* [Azure Resource Provider Contract][3] - -ARM RPs are Azure Fundamentals requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. - -### Advice for new services - -Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. - -> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. - -* Think about naming from the context of a **developer experience**. - * Start with the "things" your API manipulates, then think about the operations that a developer needs to do to these "things". - * Keep the verbs present-tense. Avoid the use of past or future tense in most cases. - * Avoid the use of generic names like "Object", "Job", "Task", "Operation" (for example - the list is not exhaustive). - * What happens to the names when the focus of the service expands? It may be worth starting with a less generic name to avoid a breaking change later on. -* Think about the code that a customer will write both before and after the REST API call. How will a developer use this API in the canonical use case? - * Consider multiple languages, and include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). -* Use previews to get the shape right. There are different notification, breaking change, and lifetime requirements on preview API versions. - * We recommend a minimum of 2 preview versions prior to your first GA release. However, there is no hard rule for previews. - * You should gather feedback from your customers and iterate until the API is useful. Actively solicit feedback from your preview customers. - -Preventing future breaking changes is a source of concern during initial review. Without a history, the review process attempts to identify patterns that may result in breaking changes later on. For instance, think about the following: - -* [Think about idempotency](https://stripe.com/blog/idempotency). In a distributed cloud, each HTTP call must be idempotent. You must be resilient in the face of failure. A developer must rely on idempotency to build fault-tolerant systems. - * The HTTP specification requires that GET, PUT, DELETE, and HEAD be idempotent. - * Prefer allowing the developer to use PUT or PATCH to create a resource with a user-specified name or ID. A developer will commonly want to download a specific resource by name or ID. This provides the developer with an idempotent mechanism for creating resources. -* Collections are a common source of review comments: - * Return collections with server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. - * A collection should return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. - * Think about how the developer can reason about the collection organization. Filtering is a common customer request. -* Avoid polymorphism. An endpoint should work with a single type to avoid problems during SDK creation. Remember that a change to the model is a breaking change. -* Use extensible enumerations unless you are completely sure that the enumeration will never expand. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. -* Implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. -* If your API specifies access conditions to another resource: - * Think about how to represent that model polymorphically. For example, you may be using a SQL Azure connection now, but extend to Cosmos DB, Azure Data Lake, or Redis Cache later on. Think about how you can specify that resource in a non-breaking manner. - * Implement managed identity access controls for accessing the other resource. Do not accept connection strings as a method of specifying access permissions. -* Be concerned about data widths of numeric types. Wider data types (e.g. 64-bit vs. 32-bit) are more future-proof. -* Think about how the interface will be represented by an SDK. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. -* Implement (and encourage) the use of PATCH for resource modifications. - * The PATCH operation should be able to modify any mutable property on the resource. - * Prefer JSON merge-patch ([RFC 7396](https://tools.ietf.org/html/rfc7396)) over JSON patch ([RFC 6902](https://tools.ietf.org/html/rfc6902)) as the accepted data format for PATCH operations. -* Get a security review of your API. You should be especially concerned with PII leakage, GDPR compliance and any other compliance regulations appropriate to your situation. - -Additionally, for management APIs: - -* Follow the advice in the [Azure Resource Manager Wiki][2] (internal only). -* Use [RPaaS](https://armwiki.azurewebsites.net/rpaas/overview.html) (internal only) to implement the Azure Resource Provider. - -## API definition - -All Services **MUST** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. - -## URL structure - -In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services - -All services **MUST** expose their service to developers via the following URL pattern: - -```text -https://.// -``` - -Where: - -* **service** - the name of the service such as "blobstore", "servicebus", "directory", or "management" -* **cloud-instance** - the DNS domain name at the root of the cloud instance. For instance, public Azure uses `azure.net`. Sovereign clouds uses different domains. -* **service-defined-root** - the root of the service-specific path, such as "blobcontainer", "myqueue", etc. -* **unit-of-multi-tenancy** - refers to a globally unique moniker that identifies a unique container in the Azure service that has the following properties: - - * This container is the boundary of isolation between different tenants of the service. - * Quotas as set and enforced at the level of this container - but there will be different limits for different operations; and operations will be service specific. - * Resources in the service are attached to this container and are tied to this container in terms of lifecycle. For example someone signs up, they get this container. If they unsubscribe (or don’t pay their bills) then cleanup of this container occurs and the resources associated with this container are cleaned up. Cleanup follows a state machine – the container and the resources attached to it are deactivated first (and can be easily restored if required), and if no response for some period then deleted. - * It is the container for billing – which means the owner of this container sees one bill for the resource usage of all azure services under this container’s identifier. - -For Azure PaaS services like SQL Azure, Azure Storage, Caching, etc., the unit of multi-tenancy is the Azure subscription id, which is a GUID. This ensures consistent access using the same URL pattern, and identifier, across all these services. - -When services produce URLs in response headers or bodies, they **MUST** use a consistent form – either always a GUID for tenant identifier or always a single verified domain - regardless of the URL used to reach the resource. - -### Direct endpoint URLs - -In addition to the required format above, services **MAY** also choose to expose direct endpoint for performance or routing reasons. The direct endpoint should be discoverable by clients, to ensure that developers are presented with a consistent pattern for accessing Azure services. - -The format of the root of the direct endpoint **MUST** be as follows: - -```text -https://-..azure.net -``` - -1. A request is made to the default endpoint (GET or HEAD). For example: - - ```text - GET https://blobstore.azure.net/contoso.com/account1/container1/blob2 - ``` - -2. That request is returned with the `Content-Location` header set to the direct endpoint. See [RFC2557]: - - ```text - 200 OK - Content-Location: https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 - ``` - - Or, with the GUID format: - - ```text - 200 OK - Content-Location: https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 - ``` - -## Versioning - -All Azure APIs **MUST** use explicit versioning. The Microsoft REST API guidelines offer different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes updates to those guidelines to ensure consistency between Azure services across Azure Stack, public Azure, and sovereign clouds. - -Retirement of an API version must follow the standard [_Azure Global Retirements and Breaking Changes_][7] policies in effect. - -### Specifying the version in Azure - -The Microsoft REST API guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: - -```text -GET https://blobstore.azure.com/foo.com/acct1/c1/blob2?api-version=1.0 -PUT https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 -POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 -``` - -### API Changes that require a version change - -There are three groups of changes that may happen to an API. - -1. Changes made to an **EXISTING** API version due to security or compliance reasons. We shall refer to these types of changes as _Compliance changes_. -2. Changes made to an API that may cause a client making the API call to fail, such as removal of an endpoint or property or changing the format of the body. We refer to these types of changes as _Breaking changes_. -3. Additive changes made to an API that do not cause a client making the API call to fail, such as the addition of a new optional property or a new endpoint. We refer to these types of changes as _Evolutionary changes_. - -With the exception of _Compliance changes_ (which are extremely rare), Azure services **MUST** update the version number of their API whenever there is a change to the API, no matter how small. Customers will "lock the API version" so that their code does not fail when the service introduces new features. They rely on the fact that an API version is a contract with the services that will never change. - -A _breaking change_ is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Removing an endpoint from an API is always a _breaking change_. Adding a new endpoint is always an _evolutionary change_. Changes to properties may be _evolutionary_ or _breaking_ depending on the type of change and whether the change is to an input parameter or output parameter: - -| Property change | Input | Output | -|:-----------------------|:------------:|:------------:| -| Remove a property | Breaking | Breaking | -| Add optional property | Evolutionary | Breaking | -| Add required property | Breaking | Breaking | -| Data type change | Breaking | Breaking | -| Format change | Breaking | Breaking | -| Integer widens | Evolutionary | Breaking | -| Integer narrows | Breaking | Evolutionary | -| Add new value to enum | Evolutionary | Breaking | -| Remove value from enum | Breaking | Breaking | -| Optional to required | Breaking | Breaking | -| Required to optional | Evolutionary | Breaking | - -Breaking changes require prior approval of the Azure REST API review board and approval through the [Azure Global Breaking Change Policy][7]. In the case of deprecation, follow the [Azure Global Retirement Policy][7]. If the service is using SemVer for versioning, breaking changes constitute a major version change. - -Evolutionary changes do not require prior approval (but still need a version bump). If the service is using SemVer for versioning, evolutionary changes constitute a minor version change. - -#### Changing the API without changing the version - -Because the API version represents a contract that a developer can rely on when generating SDKs to communicate with the service, there are a limited set of situations where changing the API is permissible without a version bump. The only changes universally allowed: - -1. Adding a new (optional) value to an extensible enum. - -An extensible enum is (in essence) a string. The values of the extensible enum drive intellisense and documentation, but the values are not considered exhaustive. - -If a service is **ONLY** available in the Azure public cloud, then an additional situation can be used to add functionality without changing the version: - -1. Adding a new (optional) query parameter to adjust the output (for example, adding filter options to a list operation). -2. Adding optional computed read-only output values that are generated based on the new (optional) query parameters. - -For example, let's say an image service wants to add bounding-box information to the output of an operation. The service can add a new query parameter `includeBoundingBox=true` and then include the bounding box information within the output only when the new query parameter is specified. A version bump is recommended, but not required. If not changing the API version, the service **MUST** update all data centers before the new query parameter is advertised to customers. - -> **DO NOT** use this mechanism just to get around the version bump. Adding such query parameters results in sub-optimal API designs and should only be used for exceptional circumstances. - -This functionality is only available for single cloud deployments because the API version specifies the contract with the developer. Consider, for example, if such a functionality was included in Azure public cloud and not a sovereign cloud. A developer creating an SDK based on this functionality may see the application work in one cloud but fail when targeting the other despite using the same API version in both cases. For the purposes of this situation, "other clouds" includes Azure Stack and other deployment mechanisms such as containers for on-premise usage. - -Do not add computed output values if the computed value can be calculated from other information in the payload. It unnecessarily expands the payload. - -All situations where the API definition is changed (irrespective of whether a version change happens or not) **MUST** be reviewed by the Azure REST API Review Board before release. - -### Preview Versions - -Preview versions of the API can be indicated by adding the suffix `-preview.X` to the end of the API version, where `X` is an incrementing integer. For example: - -* `2020-05-01-preview.1` -* `1.0-preview.2` - -Preview versions are not treated the same way as release versions. In general, there are two types of previews: - -* **Private** previews are released to a known subset of users. The service team knows how to contact each person within the private preview. There are no restrictions on changes within a private preview, as long as the service team communicates effectively with their users on what changes are made and when they will be made. - -* **Public** previews are released broadly, but contain APIs that may change between previews and may be deleted prior to the final version. The `X` integer (indicating the revision of the preview) must be incremented for all breaking changes (resulting in a new API version). Evolutionary changes may be added as needed, as long as the change is communicated broadly. - -You should follow the axiom "don't surprise your customers" when deciding whether to increment the preview version, and err on the side of incrementing the preview version. - -Preview APIs [follow a different path for deprecation and retirement](https://dev.azure.com/msazure/AzureWiki/_wiki/wikis/AzureWiki.wiki/37683/Retirement-of-Previews), and are not subject to the same guarantees as APIs marked as generally available. Preview APIs have a life-span of not more than 12 months, after which they must be retired. - -#### Why Azure recommends conservative API versioning - -Azure history is replete with anecdotes that directly relate to API versioning. For instance, Cognitive Services unintentionally broke customers by making changes to the API structure without a version bump with updates that they did not think would be breaking changes. These changes led customers to question the stability and maturity of the product and increased the churn rate for the services. - -Even changes that are evolutionary can cause problems. For instance, let's say that a service adds a new feature via a new endpoint in the API. The SDK gets updates to support this new API, but the service does not bump the version number. Since the roll out of the new feature is not atomic, there is a period of time (potentially months long) where the feature is available in some regions but not others. A customer has the potential for attempting to use the feature in two different regions and having it work in one region but not the other, despite the two regions supporting the same version number. This is only made worse when we consider Azure Stack, which can be upwards of a year behind the public cloud offerings. - -There are a few mechanisms that can reduce breaking changes and their effects on our customers. - -#### Use PATCH instead of PUT for updates - -The HTTP PUT verb is an idempotent apply operation for the API version being used. The [Microsoft API Guidelines already recommend the use of PATCH](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#742-patch) for updates. - -Consider the following sequence: - -* User1 creates a resource with version v2, using a new optional parameter. -* Later, User2 wants to update the resource using unrelated settings. Using version v1, User2 issues a GET, does the changes, and then issues a PUT to replace the resource definition. - -In this case, the optional parameter is lost because of the replace semantics. The optional parameter only exists on API version v2, and not on version v1. - -Service teams SHOULD prefer and recommend PATCH operations for updating resources. - -#### Use extensible enums - -While removing a value from an enum is a breaking change, adding an enum can be handled with an _extensible enum_. An extensible enum is a string value that has been marked with a special marker - setting `modelAsString` to true within an `x-ms-enum` block. For example: - -```json -"createdByType": { - "type": "string", - "description": "The type of identity that created the resource.", - "enum": [ - "User", - "Application", - "ManagedIdentity", - "Key" - ], - "x-ms-enum": { - "name": "createdByType", - "modelAsString": true - } -} -``` - -Always model an enum as a string unless you are positive that the symbol set will **NEVER** change over time. - -### Group versioning in Azure and Azure Stack - -Azure Stack allows customers and hosters to deploy their own small versions of Azure and upgrade it at a different pace than Azure. In order to make it possible to write an application or SDK that targets both Azure and Azure Stack, additional versioning policy is necessary. Contact the Azure Stack team for further guidance. - -### Version discovery - -Simpler clients may be hardcoded to a single version of a service. Since Azure services offer each version for a well-known period of time, a client that’s regularly maintained can be always operational without further complexity as long as during regular maintenance the client is moved forward to new versions in advance of older ones being retired. - -API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. - -Azure services **SHOULD** support API version discovery. If they support it: - -1. Services **MUST** support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service -2. Services **MUST** include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. -3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it **MUST** include those versions (group and major.minor) in the `api-deprecated-versions` header. -4. In addition to the functionality described here, services **MAY** support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. -5. Services **MAY** allow unauthenticated HTTP `OPTIONS` requests. When doing so, authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. -6. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. -7. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request **SHOULD** return the group versions that are supported across the expanded set. - -Example request to discover API versions (blob storage container list API): - -```text -OPTIONS /?comp=list HTTP/1.1 -host: accountname.blob.core.azure.net -``` - -Example response: - -```text -200 OK -api-supported-versions: 2011-08,2012-02,1.1,2.0 -api-deprecated-versions: 2009-04,1.0 -Content-Length: 0 -``` - -Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. - -## Long running operations - -The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. - -## Repeatability of requests - -The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. - -A service **SHOULD** support repeatable requests as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). - -- The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. -- A service advertises support for repeatability requests by adding the `Repeatbility-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. -- When supported, all endpoints co-located behind a DNS name **MUST** support the header. This means that a service **MUST NOT** ignore the presence of a header for any endpoints behind the DNS name, but rather fail the request containing a `Repeatability-Request-ID` header if that particular endpoint lacks support for repeatable requests. Such partial support **SHOULD** be avoided due to the confusion it causes for clients. - -## Retiring pre-release and beta APIs - -Pre-release and beta APIs are not covered by the Azure Global Retirement and Deprecation Policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. - -Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy. **Customers have suffered downtime because of deprecation of preview APIs**. - - -[1]: https://github.com/microsoft/api-guidelines -[RFC2557]: https://www.ietf.org/rfc/rfc2557.txt - - -[2]: https://aka.ms/armwiki -[3]: https://github.com/Azure/azure-resource-manager-rpc - - -[OpenAPI Specification]: https://github.com/Azure/adx-documentation-pr/wiki/Getting-started-with-OpenAPI-specifications - - -[6]: https://support.microsoft.com/en-us/help/30881 -[7]: http://aka.ms/aprwiki From 1f0867ca2107fd4286958940d05717ddf2bd7ea6 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 2 Jul 2021 14:35:13 -0700 Subject: [PATCH 080/729] Original guidelines --- azure/Guidelines.md | 318 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 azure/Guidelines.md diff --git a/azure/Guidelines.md b/azure/Guidelines.md new file mode 100644 index 00000000..7b79d817 --- /dev/null +++ b/azure/Guidelines.md @@ -0,0 +1,318 @@ +# Microsoft Azure REST API Guidelines + +## History + +| Date | Version | Notes | +| ----------- | ------- | --------------------------------------------------- | +| 2020-Mar-31 | v3.1 | 1st public release of the Azure REST API Guidelines | +| 2020-Jul-31 | v3.2 | Added service advice for initial versions | + +## Introduction + +The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. + +### Additional guidance for Azure Resource Manager resource providers + +Teams building ARM Resource Providers (RPs) MUST follow the additional guidance in the ARM Resource Provider Contract (RPC) and related documents. These documents can be found here. + +* [Azure Resource Manager Wiki][2] (Internal only) +* [Azure Resource Provider Contract][3] + +ARM RPs are Azure Fundamentals requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. + +### Advice for new services + +Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. + +> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. + +* Think about naming from the context of a **developer experience**. + * Start with the "things" your API manipulates, then think about the operations that a developer needs to do to these "things". + * Keep the verbs present-tense. Avoid the use of past or future tense in most cases. + * Avoid the use of generic names like "Object", "Job", "Task", "Operation" (for example - the list is not exhaustive). + * What happens to the names when the focus of the service expands? It may be worth starting with a less generic name to avoid a breaking change later on. +* Think about the code that a customer will write both before and after the REST API call. How will a developer use this API in the canonical use case? + * Consider multiple languages, and include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). +* Use previews to get the shape right. There are different notification, breaking change, and lifetime requirements on preview API versions. + * We recommend a minimum of 2 preview versions prior to your first GA release. However, there is no hard rule for previews. + * You should gather feedback from your customers and iterate until the API is useful. Actively solicit feedback from your preview customers. + +Preventing future breaking changes is a source of concern during initial review. Without a history, the review process attempts to identify patterns that may result in breaking changes later on. For instance, think about the following: + +* [Think about idempotency](https://stripe.com/blog/idempotency). In a distributed cloud, each HTTP call must be idempotent. You must be resilient in the face of failure. A developer must rely on idempotency to build fault-tolerant systems. + * The HTTP specification requires that GET, PUT, DELETE, and HEAD be idempotent. + * Prefer allowing the developer to use PUT or PATCH to create a resource with a user-specified name or ID. A developer will commonly want to download a specific resource by name or ID. This provides the developer with an idempotent mechanism for creating resources. +* Collections are a common source of review comments: + * Return collections with server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. + * A collection should return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. + * Think about how the developer can reason about the collection organization. Filtering is a common customer request. +* Avoid polymorphism. An endpoint should work with a single type to avoid problems during SDK creation. Remember that a change to the model is a breaking change. +* Use extensible enumerations unless you are completely sure that the enumeration will never expand. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. +* Implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. +* If your API specifies access conditions to another resource: + * Think about how to represent that model polymorphically. For example, you may be using a SQL Azure connection now, but extend to Cosmos DB, Azure Data Lake, or Redis Cache later on. Think about how you can specify that resource in a non-breaking manner. + * Implement managed identity access controls for accessing the other resource. Do not accept connection strings as a method of specifying access permissions. +* Be concerned about data widths of numeric types. Wider data types (e.g. 64-bit vs. 32-bit) are more future-proof. +* Think about how the interface will be represented by an SDK. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. +* Implement (and encourage) the use of PATCH for resource modifications. + * The PATCH operation should be able to modify any mutable property on the resource. + * Prefer JSON merge-patch ([RFC 7396](https://tools.ietf.org/html/rfc7396)) over JSON patch ([RFC 6902](https://tools.ietf.org/html/rfc6902)) as the accepted data format for PATCH operations. +* Get a security review of your API. You should be especially concerned with PII leakage, GDPR compliance and any other compliance regulations appropriate to your situation. + +Additionally, for management APIs: + +* Follow the advice in the [Azure Resource Manager Wiki][2] (internal only). +* Use [RPaaS](https://armwiki.azurewebsites.net/rpaas/overview.html) (internal only) to implement the Azure Resource Provider. + +## API definition + +All Services **MUST** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. + +## URL structure + +In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services + +All services **MUST** expose their service to developers via the following URL pattern: + +```text +https://.// +``` + +Where: + +* **service** - the name of the service such as "blobstore", "servicebus", "directory", or "management" +* **cloud-instance** - the DNS domain name at the root of the cloud instance. For instance, public Azure uses `azure.net`. Sovereign clouds uses different domains. +* **service-defined-root** - the root of the service-specific path, such as "blobcontainer", "myqueue", etc. +* **unit-of-multi-tenancy** - refers to a globally unique moniker that identifies a unique container in the Azure service that has the following properties: + + * This container is the boundary of isolation between different tenants of the service. + * Quotas as set and enforced at the level of this container - but there will be different limits for different operations; and operations will be service specific. + * Resources in the service are attached to this container and are tied to this container in terms of lifecycle. For example someone signs up, they get this container. If they unsubscribe (or don’t pay their bills) then cleanup of this container occurs and the resources associated with this container are cleaned up. Cleanup follows a state machine – the container and the resources attached to it are deactivated first (and can be easily restored if required), and if no response for some period then deleted. + * It is the container for billing – which means the owner of this container sees one bill for the resource usage of all azure services under this container’s identifier. + +For Azure PaaS services like SQL Azure, Azure Storage, Caching, etc., the unit of multi-tenancy is the Azure subscription id, which is a GUID. This ensures consistent access using the same URL pattern, and identifier, across all these services. + +When services produce URLs in response headers or bodies, they **MUST** use a consistent form – either always a GUID for tenant identifier or always a single verified domain - regardless of the URL used to reach the resource. + +### Direct endpoint URLs + +In addition to the required format above, services **MAY** also choose to expose direct endpoint for performance or routing reasons. The direct endpoint should be discoverable by clients, to ensure that developers are presented with a consistent pattern for accessing Azure services. + +The format of the root of the direct endpoint **MUST** be as follows: + +```text +https://-..azure.net +``` + +1. A request is made to the default endpoint (GET or HEAD). For example: + + ```text + GET https://blobstore.azure.net/contoso.com/account1/container1/blob2 + ``` + +2. That request is returned with the `Content-Location` header set to the direct endpoint. See [RFC2557]: + + ```text + 200 OK + Content-Location: https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 + ``` + + Or, with the GUID format: + + ```text + 200 OK + Content-Location: https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 + ``` + +## Versioning + +All Azure APIs **MUST** use explicit versioning. The Microsoft REST API guidelines offer different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes updates to those guidelines to ensure consistency between Azure services across Azure Stack, public Azure, and sovereign clouds. + +Retirement of an API version must follow the standard [_Azure Global Retirements and Breaking Changes_][7] policies in effect. + +### Specifying the version in Azure + +The Microsoft REST API guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: + +```text +GET https://blobstore.azure.com/foo.com/acct1/c1/blob2?api-version=1.0 +PUT https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 +POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 +``` + +### API Changes that require a version change + +There are three groups of changes that may happen to an API. + +1. Changes made to an **EXISTING** API version due to security or compliance reasons. We shall refer to these types of changes as _Compliance changes_. +2. Changes made to an API that may cause a client making the API call to fail, such as removal of an endpoint or property or changing the format of the body. We refer to these types of changes as _Breaking changes_. +3. Additive changes made to an API that do not cause a client making the API call to fail, such as the addition of a new optional property or a new endpoint. We refer to these types of changes as _Evolutionary changes_. + +With the exception of _Compliance changes_ (which are extremely rare), Azure services **MUST** update the version number of their API whenever there is a change to the API, no matter how small. Customers will "lock the API version" so that their code does not fail when the service introduces new features. They rely on the fact that an API version is a contract with the services that will never change. + +A _breaking change_ is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Removing an endpoint from an API is always a _breaking change_. Adding a new endpoint is always an _evolutionary change_. Changes to properties may be _evolutionary_ or _breaking_ depending on the type of change and whether the change is to an input parameter or output parameter: + +| Property change | Input | Output | +|:-----------------------|:------------:|:------------:| +| Remove a property | Breaking | Breaking | +| Add optional property | Evolutionary | Breaking | +| Add required property | Breaking | Breaking | +| Data type change | Breaking | Breaking | +| Format change | Breaking | Breaking | +| Integer widens | Evolutionary | Breaking | +| Integer narrows | Breaking | Evolutionary | +| Add new value to enum | Evolutionary | Breaking | +| Remove value from enum | Breaking | Breaking | +| Optional to required | Breaking | Breaking | +| Required to optional | Evolutionary | Breaking | + +Breaking changes require prior approval of the Azure REST API review board and approval through the [Azure Global Breaking Change Policy][7]. In the case of deprecation, follow the [Azure Global Retirement Policy][7]. If the service is using SemVer for versioning, breaking changes constitute a major version change. + +Evolutionary changes do not require prior approval (but still need a version bump). If the service is using SemVer for versioning, evolutionary changes constitute a minor version change. + +#### Changing the API without changing the version + +Because the API version represents a contract that a developer can rely on when generating SDKs to communicate with the service, there are a limited set of situations where changing the API is permissable without a version bump. The only changes universally allowed: + +1. Adding a new (optional) value to an extensible enum. + +An extensible enum is (in essence) a string. The values of the extensible enum drive intellisense and documentation, but the values are not considered exhaustive. + +If a service is **ONLY** available in the Azure public cloud, then an additional situation can be used to add functionality without changing the version: + +1. Adding a new (optional) query parameter to adjust the output (for example, adding filter options to a list operation). +2. Adding optional computed read-only output values that are generated based on the new (optional) query parameters. + +For example, let's say an image service wants to add bounding-box information to the output of an operation. The service can add a new query parameter `includeBoundingBox=true` and then include the bounding box information within the output only when the new query parameter is specified. A version bump is recommended, but not required. If not changing the API version, the service **MUST** update all data centers before the new query parameter is advertised to customers. + +> **DO NOT** use this mechanism just to get around the version bump. Adding such query parameters results in sub-optimal API designs and should only be used for exceptional circumstances. + +This functionality is only available for single cloud deployments because the API version specifies the contract with the developer. Consider, for example, if such a functionality was included in Azure public cloud and not a sovereign cloud. A developer creating an SDK based on this functionality may see the application work in one cloud but fail when targeting the other despite using the same API version in both cases. For the purposes of this situation, "other clouds" includes Azure Stack and other deployment mechanisms such as containers for on-premise usage. + +Do not add computed output values if the computed value can be calculated from other information in the payload. It unnecessarily expands the payload. + +All situations where the API definition is changed (irrespective of whether a version change happens or not) **MUST** be reviewed by the Azure REST API Review Board before release. + +### Preview Versions + +Preview versions of the API can be indicated by adding the suffix `-preview.X` to the end of the API version, where `X` is an incrementing integer. For example: + +* `2020-05-01-preview.1` +* `1.0-preview.2` + +Preview versions are not treated the same way as release versions. In general, there are two types of previews: + +* **Private** previews are released to a known subset of users. The service team knows how to contact each person within the private preview. There are no restrictions on changes within a private preview, as long as the service team communicates effectively with their users on what changes are made and when they will be made. + +* **Public** previews are released broadly, but contain APIs that may change between previews and may be deleted prior to the final version. The `X` integer (indicating the revision of the preview) must be incremented for all breaking changes (resulting in a new API version). Evolutionary changes may be added as needed, as long as the change is communicated broadly. + +You should follow the axiom "don't surprise your customers" when deciding whether to increment the preview version, and err on the side of incrementing the preview version. + +Preview APIs [follow a different path for deprecation and retirement](https://dev.azure.com/msazure/AzureWiki/_wiki/wikis/AzureWiki.wiki/37683/Retirement-of-Previews), and are not subject to the same guarantees as APIs marked as generally available. Preview APIs have a life-span of not more than 12 months, after which they must be retired. + +#### Why Azure recommends conservative API versioning + +Azure history is replete with anecdotes that directly relate to API versioning. For instance, Cognitive Services unintentionally broke customers by making changes to the API structure without a version bump with updates that they did not think would be breaking changes. These changes led customers to question the stability and maturity of the product and increased the churn rate for the services. + +Even changes that are evolutionary can cause problems. For instance, let's say that a service adds a new feature via a new endpoint in the API. The SDK gets updates to support this new API, but the service does not bump the version number. Since the roll out of the new feature is not atomic, there is a period of time (potentially months long) where the feature is available in some regions but not others. A customer has the potential for attempting to use the feature in two different regions and having it work in one region but not the other, despite the two regions supporting the same version number. This is only made worse when we consider Azure Stack, which can be upwards of a year behind the public cloud offerings. + +There are a few mechanisms that can reduce breaking changes and their effects on our customers. + +#### Use PATCH instead of PUT for updates + +The HTTP PUT verb is an idemopotent apply operation for the API version being used. The [Microsoft API Guidelines already recommend the use of PATCH](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#742-patch) for updates. + +Consider the following sequence: + +* User1 creates a resource with version v2, using a new optional parameter. +* Later, User2 wants to update the resource using unrelated settings. Using version v1, User2 issues a GET, does the changes, and then issues a PUT to replace the resource definition. + +In this case, the optional parameter is lost because of the replace semantics. The optional parameter only exists on API version v2, and not on version v1. + +Service teams SHOULD prefer and recommend PATCH operations for updating resources. + +#### Use extensible enums + +While removing a value from an enum is a breaking change, adding an enum can be handled with an _extensible enum_. An extensible enum is a string value that has been marked with a special marker - setting `modelAsString` to true within an `x-ms-enum` block. For example: + +```json +"createdByType": { + "type": "string", + "description": "The type of identity that created the resource.", + "enum": [ + "User", + "Application", + "ManagedIdentity", + "Key" + ], + "x-ms-enum": { + "name": "createdByType", + "modelAsString": true + } +} +``` + +Always model an enum as a string unleess you are positive that the symbol set will **NEVER** change over time. + +### Group versioning in Azure and Azure Stack + +Azure Stack allows customers and hosters to deploy their own small versions of Azure and upgrade it at a different pace than Azure. In order to make it possible to write an application or SDK that targets both Azure and Azure Stack, additional versioning policy is necessary. Contact the Azure Stack team for further guidance. + +### Version discovery + +Simpler clients may be hardcoded to a single version of a service. Since Azure services offer each version for a well-known period of time, a client that’s regularly maintained can be always operational without further complexity as long as during regular maintenance the client is moved forward to new versions in advance of older ones being retired. + +API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. + +Azure services **SHOULD** support API version discovery. If they support it: + +1. Services **MUST** support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service +2. Services **MUST** include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. +3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it **MUST** include those versions (group and major.minor) in the `api-deprecated-versions` header. +4. In addition to the functionality described here, services **MAY** support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. +5. Services **MAY** allow unauthenticated HTTP `OPTIONS` requests. When doing so, authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. +6. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. +7. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request **SHOULD** return the group versions that are supported across the expanded set. + +Example request to discover API versions (blob storage container list API): + +```text +OPTIONS /?comp=list HTTP/1.1 +host: accountname.blob.core.azure.net +``` + +Example response: + +```text +200 OK +api-supported-versions: 2011-08,2012-02,1.1,2.0 +api-deprecated-versions: 2009-04,1.0 +Content-Length: 0 +``` + +Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. + +## Long running operations + +The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. + +## Retiring pre-release and beta APIs + +Pre-release and beta APIs are not covered by the Azure Global Retirement and Deprecation Policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. + +Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy. **Customers have suffered downtime because of deprecation of preview APIs**. + + +[1]: https://github.com/microsoft/api-guidelines +[RFC2557]: https://www.ietf.org/rfc/rfc2557.txt + + +[2]: https://aka.ms/armwiki +[3]: https://github.com/Azure/azure-resource-manager-rpc + + +[OpenAPI Specification]: https://github.com/Azure/adx-documentation-pr/wiki/Getting-started-with-OpenAPI-specifications + + +[6]: https://support.microsoft.com/en-us/help/30881 +[7]: http://aka.ms/aprwiki From 2aac147c126950c3b465d38446c8aed2d4de70d8 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Mon, 24 May 2021 18:40:24 -0400 Subject: [PATCH 081/729] Opened workstream to update Azure guidelines --- azure/Guidelines.md | 387 ++++++++++++++++++-------------------------- 1 file changed, 159 insertions(+), 228 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 7b79d817..cdf3145a 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,3 +1,14 @@ + + +> ### Background +> This document is a work in progress and is intended to become an updated version of the Azure REST API guidelines. It is based on the best practices for building REST APIs, the existing Azure API Guidelines, and feedback from the API Stewardship Board. Your thoughts, comments, pull requests, and all other forms of feedback are welcomed and encouraged. If you have any questions, please reach out to @Mark Weitzel. +> +> Thanks! + + +--- +
+ # Microsoft Azure REST API Guidelines ## History @@ -6,313 +17,233 @@ | ----------- | ------- | --------------------------------------------------- | | 2020-Mar-31 | v3.1 | 1st public release of the Azure REST API Guidelines | | 2020-Jul-31 | v3.2 | Added service advice for initial versions | +| 2021-May-24 | WIP | This workstream opened to update and revise the guidelines| -## Introduction -The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. +TODO: Add/expand section on using these guidelines for building general APIs. MS customers can use these to build their own services. -### Additional guidance for Azure Resource Manager resource providers -Teams building ARM Resource Providers (RPs) MUST follow the additional guidance in the ARM Resource Provider Contract (RPC) and related documents. These documents can be found here. -* [Azure Resource Manager Wiki][2] (Internal only) -* [Azure Resource Provider Contract][3] +## Introduction -ARM RPs are Azure Fundamentals requirement for Azure Services and ARM RP review is another mandatory review. Some of the guidance overlaps with general API review, but passing one review will generally make the other one go very quickly. +The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. While these guidelines represent and codify many years of experience building high performant, scalable cloud services on Azure, they are generally applicable to all APIs. Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. We welcome and encourage new ideas, input and discussion. -### Advice for new services +TODO: Add sentence on how to contribute, e.g. PRs, GH discussions, etc. Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. +> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. -> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. - -* Think about naming from the context of a **developer experience**. - * Start with the "things" your API manipulates, then think about the operations that a developer needs to do to these "things". - * Keep the verbs present-tense. Avoid the use of past or future tense in most cases. - * Avoid the use of generic names like "Object", "Job", "Task", "Operation" (for example - the list is not exhaustive). - * What happens to the names when the focus of the service expands? It may be worth starting with a less generic name to avoid a breaking change later on. -* Think about the code that a customer will write both before and after the REST API call. How will a developer use this API in the canonical use case? - * Consider multiple languages, and include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). -* Use previews to get the shape right. There are different notification, breaking change, and lifetime requirements on preview API versions. - * We recommend a minimum of 2 preview versions prior to your first GA release. However, there is no hard rule for previews. - * You should gather feedback from your customers and iterate until the API is useful. Actively solicit feedback from your preview customers. - -Preventing future breaking changes is a source of concern during initial review. Without a history, the review process attempts to identify patterns that may result in breaking changes later on. For instance, think about the following: - -* [Think about idempotency](https://stripe.com/blog/idempotency). In a distributed cloud, each HTTP call must be idempotent. You must be resilient in the face of failure. A developer must rely on idempotency to build fault-tolerant systems. - * The HTTP specification requires that GET, PUT, DELETE, and HEAD be idempotent. - * Prefer allowing the developer to use PUT or PATCH to create a resource with a user-specified name or ID. A developer will commonly want to download a specific resource by name or ID. This provides the developer with an idempotent mechanism for creating resources. -* Collections are a common source of review comments: - * Return collections with server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. - * A collection should return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. - * Think about how the developer can reason about the collection organization. Filtering is a common customer request. -* Avoid polymorphism. An endpoint should work with a single type to avoid problems during SDK creation. Remember that a change to the model is a breaking change. -* Use extensible enumerations unless you are completely sure that the enumeration will never expand. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. -* Implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. -* If your API specifies access conditions to another resource: - * Think about how to represent that model polymorphically. For example, you may be using a SQL Azure connection now, but extend to Cosmos DB, Azure Data Lake, or Redis Cache later on. Think about how you can specify that resource in a non-breaking manner. - * Implement managed identity access controls for accessing the other resource. Do not accept connection strings as a method of specifying access permissions. -* Be concerned about data widths of numeric types. Wider data types (e.g. 64-bit vs. 32-bit) are more future-proof. -* Think about how the interface will be represented by an SDK. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. -* Implement (and encourage) the use of PATCH for resource modifications. - * The PATCH operation should be able to modify any mutable property on the resource. - * Prefer JSON merge-patch ([RFC 7396](https://tools.ietf.org/html/rfc7396)) over JSON patch ([RFC 6902](https://tools.ietf.org/html/rfc6902)) as the accepted data format for PATCH operations. -* Get a security review of your API. You should be especially concerned with PII leakage, GDPR compliance and any other compliance regulations appropriate to your situation. - -Additionally, for management APIs: - -* Follow the advice in the [Azure Resource Manager Wiki][2] (internal only). -* Use [RPaaS](https://armwiki.azurewebsites.net/rpaas/overview.html) (internal only) to implement the Azure Resource Provider. - -## API definition - -All Services **MUST** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. - -## URL structure - -In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services - -All services **MUST** expose their service to developers via the following URL pattern: - -```text -https://.// -``` - -Where: -* **service** - the name of the service such as "blobstore", "servicebus", "directory", or "management" -* **cloud-instance** - the DNS domain name at the root of the cloud instance. For instance, public Azure uses `azure.net`. Sovereign clouds uses different domains. -* **service-defined-root** - the root of the service-specific path, such as "blobcontainer", "myqueue", etc. -* **unit-of-multi-tenancy** - refers to a globally unique moniker that identifies a unique container in the Azure service that has the following properties: - * This container is the boundary of isolation between different tenants of the service. - * Quotas as set and enforced at the level of this container - but there will be different limits for different operations; and operations will be service specific. - * Resources in the service are attached to this container and are tied to this container in terms of lifecycle. For example someone signs up, they get this container. If they unsubscribe (or don’t pay their bills) then cleanup of this container occurs and the resources associated with this container are cleaned up. Cleanup follows a state machine – the container and the resources attached to it are deactivated first (and can be easily restored if required), and if no response for some period then deleted. - * It is the container for billing – which means the owner of this container sees one bill for the resource usage of all azure services under this container’s identifier. -For Azure PaaS services like SQL Azure, Azure Storage, Caching, etc., the unit of multi-tenancy is the Azure subscription id, which is a GUID. This ensures consistent access using the same URL pattern, and identifier, across all these services. +### Guideline Organization +The Guiding Principles section presents the high level concerns that affect all Azure services. Adherence to these principles creates consistent design, makes it easier for developers to use your service, and reduces the learning curve for other Azure services. All of these are critical to creating a delightful experience for Azure developers. -When services produce URLs in response headers or bodies, they **MUST** use a consistent form – either always a GUID for tenant identifier or always a single verified domain - regardless of the URL used to reach the resource. +These guidelines are organized in three primary sections; Guiding Principles, Building Blocks, and Common Patterns. The Guiding Principles section will present the set of considerations that impact the overall design of your API, e.g. naming, . -### Direct endpoint URLs +Each section builds upon the other. For example, when updating resource collections, you should make sure to understand the difference in the HTTP verbs PUT and PATCH, as their behavior is quite different and directly affects how you expose the capability of your service. -In addition to the required format above, services **MAY** also choose to expose direct endpoint for performance or routing reasons. The direct endpoint should be discoverable by clients, to ensure that developers are presented with a consistent pattern for accessing Azure services. +#### Prescriptive Guidance +This document will be as prescritive as possible. Specific guideance is labelled and color-coded to show the relative importance. In order from highest importance to lowest importance: -The format of the root of the direct endpoint **MUST** be as follows: +__MUST__ adopt this guideline or follow this pattern. -```text -https://-..azure.net -``` - -1. A request is made to the default endpoint (GET or HEAD). For example: - - ```text - GET https://blobstore.azure.net/contoso.com/account1/container1/blob2 - ``` - -2. That request is returned with the `Content-Location` header set to the direct endpoint. See [RFC2557]: - - ```text - 200 OK - Content-Location: https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 - ``` - - Or, with the GUID format: - - ```text - 200 OK - Content-Location: https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 - ``` - -## Versioning - -All Azure APIs **MUST** use explicit versioning. The Microsoft REST API guidelines offer different options on how to specify an API version and guidance on what constitutes a breaking change. This section of the Azure API guidelines describes updates to those guidelines to ensure consistency between Azure services across Azure Stack, public Azure, and sovereign clouds. - -Retirement of an API version must follow the standard [_Azure Global Retirements and Breaking Changes_][7] policies in effect. - -### Specifying the version in Azure - -The Microsoft REST API guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. Azure services **MUST** use the api-version query parameter. For example: +__MUST NOT__ adopt this guideline or follow this pattern. -```text -GET https://blobstore.azure.com/foo.com/acct1/c1/blob2?api-version=1.0 -PUT https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 -POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 -``` +__SHOULD__ strongly consider this guideline. -### API Changes that require a version change +__SHOULD NOT__ strongly consider this guideline. -There are three groups of changes that may happen to an API. +__MAY__ consider this guideline if appropriate to your situation. -1. Changes made to an **EXISTING** API version due to security or compliance reasons. We shall refer to these types of changes as _Compliance changes_. -2. Changes made to an API that may cause a client making the API call to fail, such as removal of an endpoint or property or changing the format of the body. We refer to these types of changes as _Breaking changes_. -3. Additive changes made to an API that do not cause a client making the API call to fail, such as the addition of a new optional property or a new endpoint. We refer to these types of changes as _Evolutionary changes_. + If you feel you need an exception, or need clarity based on your situation, please engage with the [API Stewardship Board] prior to release of your API. -With the exception of _Compliance changes_ (which are extremely rare), Azure services **MUST** update the version number of their API whenever there is a change to the API, no matter how small. Customers will "lock the API version" so that their code does not fail when the service introduces new features. They rely on the fact that an API version is a contract with the services that will never change. -A _breaking change_ is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Removing an endpoint from an API is always a _breaking change_. Adding a new endpoint is always an _evolutionary change_. Changes to properties may be _evolutionary_ or _breaking_ depending on the type of change and whether the change is to an input parameter or output parameter: +## Guiding Principles +Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, especially over time. -| Property change | Input | Output | -|:-----------------------|:------------:|:------------:| -| Remove a property | Breaking | Breaking | -| Add optional property | Evolutionary | Breaking | -| Add required property | Breaking | Breaking | -| Data type change | Breaking | Breaking | -| Format change | Breaking | Breaking | -| Integer widens | Evolutionary | Breaking | -| Integer narrows | Breaking | Evolutionary | -| Add new value to enum | Evolutionary | Breaking | -| Remove value from enum | Breaking | Breaking | -| Optional to required | Breaking | Breaking | -| Required to optional | Evolutionary | Breaking | +This document provides Microsoft teams building Azure services with a set of guidelines that will help service teams build great APIs. The guidelines can be applied to create an API that is approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation, and enabling a broad audience of developers across multiple languages. -Breaking changes require prior approval of the Azure REST API review board and approval through the [Azure Global Breaking Change Policy][7]. In the case of deprecation, follow the [Azure Global Retirement Policy][7]. If the service is using SemVer for versioning, breaking changes constitute a major version change. +>Our goal is to create a developer friendly API where: +> * customer workloads __MUST__ never break +> * customers __MUST__ be able to adopt a new version of service or SDK w/out requiring code changes -Evolutionary changes do not require prior approval (but still need a version bump). If the service is using SemVer for versioning, evolutionary changes constitute a minor version change. +Service teams should engage the API Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. -#### Changing the API without changing the version +### Start with developer experience +A great API starts with a well thought out and designed service. It is extremely difficult, if not impossible, to create an elegant API that will work well on top of a service that is poorly designed. For example, if during a user study during a preview, you discover that customers are struggling to use your API, e.g. they don't understand the abstraction layer, take the time to fix your service. This will benefit the developer and your team. For this reason, it's important that you put yourself in the developer's shoes and think deeply about how they will be using your API and your service. -Because the API version represents a contract that a developer can rely on when generating SDKs to communicate with the service, there are a limited set of situations where changing the API is permissable without a version bump. The only changes universally allowed: +>Think about the code that a customer will write both before and after the REST API call. How will a developer use this API in the canonical use case? +> * Service teams __SHOULD__ provide examples in multiple languages, and __SHOULD__ include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). -1. Adding a new (optional) value to an extensible enum. -An extensible enum is (in essence) a string. The values of the extensible enum drive intellisense and documentation, but the values are not considered exhaustive. +### Focus on hero scenarios +We all want to get our service out into the wild as quickly as possible. We don't want to waste time and energy building things our customers never use, or that rare edge case that happens once in a blue moon. While it sounds obvious, approach the design from the customer to the service; not from service to the customer. -If a service is **ONLY** available in the Azure public cloud, then an additional situation can be used to add functionality without changing the version: +It is important to realize that writing an API is, in many cases, the easist part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documenation, and creation of client libraries and examples.Focusing on hero scenarios reduces development, support, and maintenace costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A tell tale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. Service teams: +> * __SHOULD__ define "hero scenarios" first, then the operations required, & then design the API +> * __SHOULD__ provide example code that demonstrates their "Hero Scenarios." +> * __SHOULD NOT__ add APIs for speculative features customers might want -1. Adding a new (optional) query parameter to adjust the output (for example, adding filter options to a list operation). -2. Adding optional computed read-only output values that are generated based on the new (optional) query parameters. +### Reflect key concepts through naming +TODO: Intro sentence for context -For example, let's say an image service wants to add bounding-box information to the output of an operation. The service can add a new query parameter `includeBoundingBox=true` and then include the bounding box information within the output only when the new query parameter is specified. A version bump is recommended, but not required. If not changing the API version, the service **MUST** update all data centers before the new query parameter is advertised to customers. +As you identify the tasks and activities that developers will accomplish using your service, it will be important to develop a vocabulary that intuitively reflects your core concepts. + * Start with the "things" your API manipulates, then think about the operations that a developer needs to do to these "things". + * Keep the verbs present-tense. Avoid the use of past or future tense in most cases. + * Avoid the use of generic names like "Object", "Job", "Task", "Operation" (for example - the list is not exhaustive). + * What happens to the names when the focus of the service expands? It may be worth starting with a less generic name to avoid a breaking change later on. + * +TODO: Pull in section from Heath's cognitive doc -> **DO NOT** use this mechanism just to get around the version bump. Adding such query parameters results in sub-optimal API designs and should only be used for exceptional circumstances. -This functionality is only available for single cloud deployments because the API version specifies the contract with the developer. Consider, for example, if such a functionality was included in Azure public cloud and not a sovereign cloud. A developer creating an SDK based on this functionality may see the application work in one cloud but fail when targeting the other despite using the same API version in both cases. For the purposes of this situation, "other clouds" includes Azure Stack and other deployment mechanisms such as containers for on-premise usage. +### Start with your API definition +You don't build a house without a blueprint. Neither should you build your service without a well thought-out API definition. Understanding how your service will be used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It should be reflect the naming decisions and make it easy for developers to implement your hero scenarios. +> * All Services **MUST** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. +> * All Services __SHOULD__ describe their services using ADL *[LINK TO ADL HERE]*. +> * ADL __SHOULD__ be used to generate the required OpenAPI Definition. -Do not add computed output values if the computed value can be calculated from other information in the payload. It unnecessarily expands the payload. -All situations where the API definition is changed (irrespective of whether a version change happens or not) **MUST** be reviewed by the Azure REST API Review Board before release. +### Use previews to iterate + Before releasing your API, plan to invest significant design effort, get customer feedback, & iterate through multiple previews. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. -### Preview Versions +>Use previews to get the shape right. There are different notification, breaking change, and lifetime requirements on preview API versions. +> * Service teams __SHOULD__ release and evaluate a minimum of 2 preview versions prior to the first GA release. +> * Service teams __SHOULD__ create feedback loops that actively solicit feedback from preview customers. -Preview versions of the API can be indicated by adding the suffix `-preview.X` to the end of the API version, where `X` is an incrementing integer. For example: +TODO: Provide references on how to run an effective preview -* `2020-05-01-preview.1` -* `1.0-preview.2` +### Avoid surprises +A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. +* Avoid polymorphism. An endpoint should work with a single type to avoid problems during SDK creation. Remember that a change to the model is a breaking change. +* Make Collections easy to work with. Collections are a common source of review comments. It is important to handle them in a consistent manner within your service. + * A collection __SHOULD__ return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. + * A collection __SHOULD__ support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. -Preview versions are not treated the same way as release versions. In general, there are two types of previews: +* Build in [idempotency](https://stripe.com/blog/idempotency) from the outset. In a distributed cloud, each HTTP call must be idempotent. In fact, the HTTP specification requires that GET, PUT, DELETE, and HEAD be idempotent. You must be resilient in the face of failure. Developers rely on idempotency to build fault-tolerant systems. + * Example: Allow the developer to use PUT or PATCH to create a resource with a user-specified name or ID. A developer will commonly want to download a specific resource by name or ID. This provides the developer with an idempotent mechanism for creating resources. -* **Private** previews are released to a known subset of users. The service team knows how to contact each person within the private preview. There are no restrictions on changes within a private preview, as long as the service team communicates effectively with their users on what changes are made and when they will be made. -* **Public** previews are released broadly, but contain APIs that may change between previews and may be deleted prior to the final version. The `X` integer (indicating the revision of the preview) must be incremented for all breaking changes (resulting in a new API version). Evolutionary changes may be added as needed, as long as the change is communicated broadly. +### Design for Resiliancy +As you build out your service and API, there are a number of decisions that can be made up front that add resiliancy. Addressing these as early as possible will help you iterate faster and avoid breaking changes. +* Use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. +* Implement conditional requests early. This allows you to support concurrency, which tends to be a concern later on. +* If your API specifies access conditions to another resource: +** Think about how to represent that model polymorphically. For example, you may be using a SQL Azure connection now, but extend to Cosmos DB, Azure Data Lake, or Redis Cache later on. Think about how you can specify that resource in a non-breaking manner. +* Implement managed identity access controls for accessing the other resource. Do not accept connection strings as a method of specifying access permissions. -You should follow the axiom "don't surprise your customers" when deciding whether to increment the preview version, and err on the side of incrementing the preview version. +TODO: I'd like to be much more prescriptive here. +* Be concerned about data widths of numeric types. Wider data types (e.g. 64-bit vs. 32-bit) are more future-proof. +* Think about how the interface will be represented by an SDK. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. -Preview APIs [follow a different path for deprecation and retirement](https://dev.azure.com/msazure/AzureWiki/_wiki/wikis/AzureWiki.wiki/37683/Retirement-of-Previews), and are not subject to the same guarantees as APIs marked as generally available. Preview APIs have a life-span of not more than 12 months, after which they must be retired. +### Avoid breaking changes +Innovation and design improvements to a service and its API are often the result of user studies and understanding how a service is used through telemetry. Well defined and manage previews enable rapid learning through a tight feedback loop. Being able to iterate quickly and incorporate these learnings can be a competitve differentor. However, as services mature, developers will rely more and more on the API, using them as a foundation for their own applications. For this reason, it is imperative to effectively manage change. This is especially true once a service has GA'd its API. The guidelines in this document have been designed in such a way as to help service teams balance innovation and stability. -#### Why Azure recommends conservative API versioning +> Service teams __MUST__ follow the breaking change guidelines specified in this document. *[LINK TO SECTION]* -Azure history is replete with anecdotes that directly relate to API versioning. For instance, Cognitive Services unintentionally broke customers by making changes to the API structure without a version bump with updates that they did not think would be breaking changes. These changes led customers to question the stability and maturity of the product and increased the churn rate for the services. -Even changes that are evolutionary can cause problems. For instance, let's say that a service adds a new feature via a new endpoint in the API. The SDK gets updates to support this new API, but the service does not bump the version number. Since the roll out of the new feature is not atomic, there is a period of time (potentially months long) where the feature is available in some regions but not others. A customer has the potential for attempting to use the feature in two different regions and having it work in one region but not the other, despite the two regions supporting the same version number. This is only made worse when we consider Azure Stack, which can be upwards of a year behind the public cloud offerings. -There are a few mechanisms that can reduce breaking changes and their effects on our customers. +## Building Blocks: HTTP, REST, & JSON +Purpose: Understand the building blocks. +Communicate the core concepts. +The more common patterns that APIs use are all built using these. -#### Use PATCH instead of PUT for updates -The HTTP PUT verb is an idemopotent apply operation for the API version being used. The [Microsoft API Guidelines already recommend the use of PATCH](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#742-patch) for updates. -Consider the following sequence: -* User1 creates a resource with version v2, using a new optional parameter. -* Later, User2 wants to update the resource using unrelated settings. Using version v1, User2 issues a GET, does the changes, and then issues a PUT to replace the resource definition. -In this case, the optional parameter is lost because of the replace semantics. The optional parameter only exists on API version v2, and not on version v1. +There are additional considerations for management APIs. Microsoft teams building this aspect of the service should refer to the following resources for supplemental guidelines: +* [Azure Resource Manager Wiki][2] (Microsoft only). +* Use [RPaaS](https://armwiki.azurewebsites.net/rpaas/overview.html) (Microsoft only) to implement the Azure Resource Provider. -Service teams SHOULD prefer and recommend PATCH operations for updating resources. +### HTTP -#### Use extensible enums +#### Request / Response -While removing a value from an enum is a breaking change, adding an enum can be handled with an _extensible enum_. An extensible enum is a string value that has been marked with a special marker - setting `modelAsString` to true within an `x-ms-enum` block. For example: +REQUEST +``` +POST /items?color=orange HTTP/1.1 +host: www.contoso.com:443 +accept: application/json +content-type: application/json +{ "someValue": true} +``` -```json -"createdByType": { - "type": "string", - "description": "The type of identity that created the resource.", - "enum": [ - "User", - "Application", - "ManagedIdentity", - "Key" - ], - "x-ms-enum": { - "name": "createdByType", - "modelAsString": true - } -} +RESPONSE +``` +HTTP/1.1 200 OK +etag: "511GaciaHb28" +content-length: 23 +content-type: application/json +{"someValue": true} ``` -Always model an enum as a string unleess you are positive that the symbol set will **NEVER** change over time. +> A service __MUST__ validate all inputs +> A service __MUST NOT__ include PII in the URL -### Group versioning in Azure and Azure Stack +##### Common Request & Response Headers -Azure Stack allows customers and hosters to deploy their own small versions of Azure and upgrade it at a different pace than Azure. In order to make it possible to write an application or SDK that targets both Azure and Azure Stack, additional versioning policy is necessary. Contact the Azure Stack team for further guidance. +Header Key | Applies to | Example +------------ | ------------- | ------------- +authorization | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) +x-ms-useragent | Request | (see Telemetry) +traceparent | Request | (see Distributed Tracing) +tracecontext | Request | (see Distributed Tracing) +accept | Request | application/json +if-match | Request | "67ab43" or * (no quotes) (see Conditional Access) +if-none-match | Request | "67ab43" or * (no quotes) (see Conditional Access) +date [RFC1123] | Both | Sun, 06 Nov 1994 08:49:37 GMT +content-type | Both | application/merge-patch+json +content-length | Both | 1024 +x-ms-request-id | Response | (see Customer Support) +etag | Response | "67ab43" (see Conditional Access) +retry-after | Response | 180 (see Throttling Client Requests) +x-ms-error-code | Response | (see Processing a REST Request) -### Version discovery -Simpler clients may be hardcoded to a single version of a service. Since Azure services offer each version for a well-known period of time, a client that’s regularly maintained can be always operational without further complexity as long as during regular maintenance the client is moved forward to new versions in advance of older ones being retired. +#### URLs -API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. +#### Idempotency -Azure services **SHOULD** support API version discovery. If they support it: + **Building a cloud service** +Customers can create fault-tolerant apps by supporting retries/idempotency +Remaining fault-tolerant in the face of failures +Network requests fail for many reasons +Unhandled exception, hardware failure, scale-down, code upgrade, orchestrator VM balancing, timeout, server throttling, network outage +Bottom line: a client may not get a service's response +Client code must retry to compensate for these failures. So, services must implement operations idempotently -1. Services **MUST** support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service -2. Services **MUST** include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. -3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it **MUST** include those versions (group and major.minor) in the `api-deprecated-versions` header. -4. In addition to the functionality described here, services **MAY** support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. -5. Services **MAY** allow unauthenticated HTTP `OPTIONS` requests. When doing so, authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. -6. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. -7. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request **SHOULD** return the group versions that are supported across the expanded set. +Exactly Once --> Client Retries & Service Idempotency -Example request to discover API versions (blob storage container list API): +> The problem with POST -```text -OPTIONS /?comp=list HTTP/1.1 -host: accountname.blob.core.azure.net -``` +### REST -Example response: -```text -200 OK -api-supported-versions: 2011-08,2012-02,1.1,2.0 -api-deprecated-versions: 2009-04,1.0 -Content-Length: 0 -``` -Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. +### JSON + +## Common API Patterns -## Long running operations +### Performing an Action -The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. Services **SHOULD** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. Clients that call these services **SHOULD** look for both HEADERS and prefer the `Operation-Location` version. Both HEADERS **MUST** return the same value. +### Collections -## Retiring pre-release and beta APIs +### Long Running Operations -Pre-release and beta APIs are not covered by the Azure Global Retirement and Deprecation Policy. Each team providing a preview API **SHOULD** communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. +### API Versioning -Though services may set their own deprecation policy for pre-release APIs, they should monitor these endpoints closely and consider following the normal deprecation policy. **Customers have suffered downtime because of deprecation of preview APIs**. +### Distributed Tracing & Service Telemetry - -[1]: https://github.com/microsoft/api-guidelines -[RFC2557]: https://www.ietf.org/rfc/rfc2557.txt +### Jobs +* e.g. cascading delete - -[2]: https://aka.ms/armwiki -[3]: https://github.com/Azure/azure-resource-manager-rpc +### Bring your own storage +* Getting data into your service +* Working with blobs - -[OpenAPI Specification]: https://github.com/Azure/adx-documentation-pr/wiki/Getting-started-with-OpenAPI-specifications - -[6]: https://support.microsoft.com/en-us/help/30881 -[7]: http://aka.ms/aprwiki +## Final Thoughts / Summary +* Careful consideration up front +* Long term decisions that are often codified in SDKs, CODE, etc. +* Reach out and engage the stewardship team! \ No newline at end of file From 0cfdcb551bd052e86682c0720b250d7216854962 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 25 May 2021 12:30:58 -0400 Subject: [PATCH 082/729] Added placeholder for unknown parameters --- azure/Guidelines.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index cdf3145a..022cbc60 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -219,6 +219,9 @@ Exactly Once --> Client Retries & Service Idempotency ### REST +#### Handling unknown properties or parameters +This should dovetail nicely into the added guidance on how to deal with "readOnly" values that a client may (incorrectly) supply in a request. + ### JSON From 1aa990689a6c598e8ed06f834a4db5f826cd19e7 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Wed, 26 May 2021 15:29:17 -0400 Subject: [PATCH 083/729] First pass, URL section --- azure/Guidelines.md | 113 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 87 insertions(+), 26 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 022cbc60..53baae87 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -142,44 +142,99 @@ Innovation and design improvements to a service and its API are often the result > Service teams __MUST__ follow the breaking change guidelines specified in this document. *[LINK TO SECTION]* +There are additional considerations for management APIs. Microsoft teams building this aspect of the service should refer to the following resources for supplemental guidelines: +* [Azure Resource Manager Wiki][2] (Microsoft only). +* Use [RPaaS](https://armwiki.azurewebsites.net/rpaas/overview.html) (Microsoft only) to implement the Azure Resource Provider. + ## Building Blocks: HTTP, REST, & JSON -Purpose: Understand the building blocks. -Communicate the core concepts. -The more common patterns that APIs use are all built using these. +The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet, namely HTTP, REST, and JSON. This section will provide you with a general understanding of how these technologies should be applied when creating your service. +### HTTP +Azure services will adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231), as closely possible when presenting their API. This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: +* Uniform Resource Locators (URLs) +* HTTP Methods +* Headers +* Bodies +#### URLs +A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. Becuase these will be used so heavily by developers, careful consideration should be taken when devising your structure. For these reasons, service providers __SHOULD__ keep URLs readable and if possible, avoid UUIDs & %-encoding (ex: Cádiz) +In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services. Specifically: +All services **MUST** expose their service to developers via the following URL pattern: -There are additional considerations for management APIs. Microsoft teams building this aspect of the service should refer to the following resources for supplemental guidelines: -* [Azure Resource Manager Wiki][2] (Microsoft only). -* Use [RPaaS](https://armwiki.azurewebsites.net/rpaas/overview.html) (Microsoft only) to implement the Azure Resource Provider. +```text +https://.// +``` -### HTTP +Where: -#### Request / Response +* **service** - the name of the service such as "blobstore", "servicebus", "directory", or "management" +* **cloud-instance** - the DNS domain name at the root of the cloud instance. For instance, public Azure uses `azure.net`. Sovereign clouds uses different domains. +* **service-defined-root** - the root of the service-specific path, such as "blobcontainer", "myqueue", etc. +* **unit-of-multi-tenancy** - refers to a globally unique moniker that identifies a unique container in the Azure service that has the following properties: -REQUEST -``` -POST /items?color=orange HTTP/1.1 -host: www.contoso.com:443 -accept: application/json -content-type: application/json -{ "someValue": true} -``` + * This container is the boundary of isolation between different tenants of the service. + * Quotas as set and enforced at the level of this container - but there will be different limits for different operations; and operations will be service specific. + * Resources in the service are attached to this container and are tied to this container in terms of lifecycle. For example someone signs up, they get this container. If they unsubscribe (or don’t pay their bills) then cleanup of this container occurs and the resources associated with this container are cleaned up. Cleanup follows a state machine – the container and the resources attached to it are deactivated first (and can be easily restored if required), and if no response for some period then deleted. + * It is the container for billing – which means the owner of this container sees one bill for the resource usage of all azure services under this container’s identifier. -RESPONSE -``` -HTTP/1.1 200 OK -etag: "511GaciaHb28" -content-length: 23 -content-type: application/json -{"someValue": true} +For Azure PaaS services like SQL Azure, Azure Storage, Caching, etc., the unit of multi-tenancy is the Azure subscription id, which is a GUID. This ensures consistent access using the same URL pattern, and identifier, across all these services. + +##### Additional URL considerations +When services produce URLs in response headers or bodies, they **MUST** use a consistent form – either always a GUID for tenant identifier or always a single verified domain - regardless of the URL used to reach the resource. + +It is common that resources will differ by case. In addition, case may also affect computed values. Therefore, a service URL must be case-sensitive (except for scheme/host). If case doesn't match what you expect, the request __MUST__ fail with the appropriate HTTP return code. Further, when you are returning information in a Response, services __MUST__ maintain and respect proper case values. + +Logging of URLs is another common practice. We want to take every precaution to prevent the leakage of Personal Identifying Information (PII). Azure services __MUST NOT__ include Personal Identifying Information (PII) in the URL. + +The Max length=2083 characters __MUST__ be observed. If a URL excedes this length, the service __MUST__ return a ```414-URI Too Long``` + +Legal characters for a URL ar: 0-9 A-Z a-z - . _ ~ / ? # [ ] @ ! $ & ' ( ) * + , ; = +Services __SHOULD__ reserve the following characters for use exclusive use: ```/ ? # [ ] @ ! $ & ' ( ) * + , ; =``` + + +### Direct endpoint URLs + +In addition to the required format above, services **MAY** also choose to expose direct endpoint for performance or routing reasons. The direct endpoint should be discoverable by clients, to ensure that developers are presented with a consistent pattern for accessing Azure services. + +The format of the root of the direct endpoint **MUST** be as follows: + +```text +https://-..azure.net ``` -> A service __MUST__ validate all inputs -> A service __MUST NOT__ include PII in the URL +1. A request is made to the default endpoint (GET or HEAD). For example: + + ```text + GET https://blobstore.azure.net/contoso.com/account1/container1/blob2 + ``` + +2. That request is returned with the `Content-Location` header set to the direct endpoint. See [RFC2557]: + + ```text + 200 OK + Content-Location: https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 + ``` + + Or, with the GUID format: + + ```text + 200 OK + Content-Location: https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 + ``` + + +### HTTP Request / Response +The HTTP Request / Response pattern will dictate much of how your API behaves. + +> * A service __MUST__ validate all inputs. +> * For create and upsert operations, a service __SHOULD__ return the same object that was sent to the API. + + + + ##### Common Request & Response Headers @@ -201,7 +256,7 @@ retry-after | Response | 180 (see Throttling Client Requests) x-ms-error-code | Response | (see Processing a REST Request) -#### URLs + #### Idempotency @@ -217,8 +272,14 @@ Exactly Once --> Client Retries & Service Idempotency > The problem with POST +#### Additional References +* [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) + ### REST + + + #### Handling unknown properties or parameters This should dovetail nicely into the added guidance on how to deal with "readOnly" values that a client may (incorrectly) supply in a request. From c798e0d0f1682f2e7fa9ebb2b825afff3c16d44a Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Wed, 26 May 2021 18:55:07 -0400 Subject: [PATCH 084/729] First pass, HTTP section --- azure/Guidelines.md | 96 ++++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 53baae87..74e23232 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -28,7 +28,7 @@ TODO: Add/expand section on using these guidelines for building general APIs. MS The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. While these guidelines represent and codify many years of experience building high performant, scalable cloud services on Azure, they are generally applicable to all APIs. Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. We welcome and encourage new ideas, input and discussion. -TODO: Add sentence on how to contribute, e.g. PRs, GH discussions, etc. +TODO: Add sentence on how to contribute, e.g. PRs, GH discussions, etc. Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. > A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. @@ -86,7 +86,7 @@ It is important to realize that writing an API is, in many cases, the easist par > * __SHOULD NOT__ add APIs for speculative features customers might want ### Reflect key concepts through naming -TODO: Intro sentence for context +TODO: Intro sentence for context As you identify the tasks and activities that developers will accomplish using your service, it will be important to develop a vocabulary that intuitively reflects your core concepts. * Start with the "things" your API manipulates, then think about the operations that a developer needs to do to these "things". @@ -94,7 +94,7 @@ As you identify the tasks and activities that developers will accomplish using y * Avoid the use of generic names like "Object", "Job", "Task", "Operation" (for example - the list is not exhaustive). * What happens to the names when the focus of the service expands? It may be worth starting with a less generic name to avoid a breaking change later on. * -TODO: Pull in section from Heath's cognitive doc +TODO: Pull in section from Heath's doc ### Start with your API definition @@ -111,7 +111,7 @@ You don't build a house without a blueprint. Neither should you build your servi > * Service teams __SHOULD__ release and evaluate a minimum of 2 preview versions prior to the first GA release. > * Service teams __SHOULD__ create feedback loops that actively solicit feedback from preview customers. -TODO: Provide references on how to run an effective preview +TODO: Provide references on how to run an effective preview ### Avoid surprises A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. @@ -132,7 +132,7 @@ As you build out your service and API, there are a number of decisions that can ** Think about how to represent that model polymorphically. For example, you may be using a SQL Azure connection now, but extend to Cosmos DB, Azure Data Lake, or Redis Cache later on. Think about how you can specify that resource in a non-breaking manner. * Implement managed identity access controls for accessing the other resource. Do not accept connection strings as a method of specifying access permissions. -TODO: I'd like to be much more prescriptive here. +TODO: I'd like to be much more prescriptive here. * Be concerned about data widths of numeric types. Wider data types (e.g. 64-bit vs. 32-bit) are more future-proof. * Think about how the interface will be represented by an SDK. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. @@ -226,54 +226,78 @@ https://-..azure.net ``` -### HTTP Request / Response -The HTTP Request / Response pattern will dictate much of how your API behaves. +### HTTP Request / Response Pattern +The HTTP Request / Response pattern will dictate much of how your API behaves, for example; POST methods must be idempotent, GET methods may be cached, the If-Modified and etag headers determine your optimistic concurrency strategy. The URL of a service, along with its request / response, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern __SHOULD__ be one of the first implementation decisions you will make. For each request / response, the service: -> * A service __MUST__ validate all inputs. -> * For create and upsert operations, a service __SHOULD__ return the same object that was sent to the API. +> * __MUST__ validate all inputs to a request. +> * __SHOULD__ return the same object that was sent to the API in the response for all create or upsert operations. +Because beacuse information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. Service provides __MUST__ use the following table when translating strings: +Data type | Document string must be +-------- | ------- +Boolean | true / false +Integer | -253+1 to +253-1 (limit due to IEEE-754 [RFC8259]())https://datatracker.ietf.org/doc/html/rfc8259) +Float | [IEEE-754 binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) +String | (Un)quoted?, max length, case-sensitive, multiple delimiter +UUID | {}? casing? hyphens? [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) +Date/Time (Header) | [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123) +Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) +Byte array | Base-64 encoded, max length +TODO: Expand the explanation for numbers. +#### Common Request & Response Headers +The table below lists the request / response headers most used by Azure services Service providers __SHOULD__ properly handle all headers annotated in *italics*. In addition, each request / response header: +> * __MUST__ be specificed using kabob-style-text +> * __MUST__ be all lowercase +> * __SHOULD NOT__ use "x-" prefix, unless already existing in production -##### Common Request & Response Headers Header Key | Applies to | Example ------------ | ------------- | ------------- -authorization | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) -x-ms-useragent | Request | (see Telemetry) -traceparent | Request | (see Distributed Tracing) -tracecontext | Request | (see Distributed Tracing) +*authorization* | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) +*x-ms-useragent* | Request | [see Telemetry](http://TODO:link-goes-here) +traceparent | Request | [see Distributed Tracing]](http://TODO:link-goes-here) +tracecontext | Request | [see Distributed Tracing](http://TODO:link-goes-here) accept | Request | application/json if-match | Request | "67ab43" or * (no quotes) (see Conditional Access) -if-none-match | Request | "67ab43" or * (no quotes) (see Conditional Access) +if-none-match | Request | "67ab43" or * (no quotes) [see Conditional Access](http://TODO:link-goes-here) +If-Modified-Since | | Request | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) +If-Unmodified-Since | Request | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) date [RFC1123] | Both | Sun, 06 Nov 1994 08:49:37 GMT -content-type | Both | application/merge-patch+json -content-length | Both | 1024 -x-ms-request-id | Response | (see Customer Support) -etag | Response | "67ab43" (see Conditional Access) -retry-after | Response | 180 (see Throttling Client Requests) -x-ms-error-code | Response | (see Processing a REST Request) +*content-type* | Both | application/merge-patch+json +*content-length* | Both | 1024 +*x-ms-request-id* | Response | [see Customer Support](http://TODO:link-goes-here) +etag | Response | "67ab43" [see Conditional Access](http://TODO:link-goes-here) +retry-after | Response | 180 [see Throttling Client Requests] +*x-ms-error-code* | Response | [see Processing a REST Request](http://TODO:link-goes-here) +Last-Modified | Response | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) +TODO: Fix the links. -#### Idempotency +#### HTTP methods & idempotency +[Idempotentency](https://www.lexico.com/en/definition/idempotent), or the ability for the state of resource to remain unchanged when the same operation is applied, is a fundamental property of resilient cloud services. Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. (See our guiding principle of "No surprises".) - **Building a cloud service** -Customers can create fault-tolerant apps by supporting retries/idempotency -Remaining fault-tolerant in the face of failures -Network requests fail for many reasons -Unhandled exception, hardware failure, scale-down, code upgrade, orchestrator VM balancing, timeout, server throttling, network outage -Bottom line: a client may not get a service's response -Client code must retry to compensate for these failures. So, services must implement operations idempotently -Exactly Once --> Client Retries & Service Idempotency +Method | Description | Response Status Code +----|----|---- +GET | Read the resource | 200-OK +DELETE | Remove the resource | 204-No Content; avoid 404-Not Found +PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created +PUT | Create/Replace the *whole* resource | 200-OK, 201-Created -> The problem with POST +> * Service providers __MUST__ implement all operations idempotently. +> * Service providers __SHOULD__ avoid using POST unless it can be implemented idempotently. #### Additional References * [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) +* [Standard HTTP Headers](https://httpwg.org/specs/rfc7231.html#header.field.registration) +* [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) + + ### REST @@ -306,8 +330,16 @@ This should dovetail nicely into the added guidance on how to deal with "readOnl * Getting data into your service * Working with blobs +### Optimistic concurrency + ## Final Thoughts / Summary * Careful consideration up front * Long term decisions that are often codified in SDKs, CODE, etc. -* Reach out and engage the stewardship team! \ No newline at end of file +* Reach out and engage the stewardship team! + + +## API Guidelines Quick Reference Sheet +TODO: Should we create a quick reference sheet?? +Add the Must / Must NOT w/links +See if we can generate this \ No newline at end of file From 7011a085f548e41659ff030608008f162f763d68 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Wed, 26 May 2021 18:59:46 -0400 Subject: [PATCH 085/729] Minor update to background text --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 74e23232..a0482346 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,7 +1,7 @@ > ### Background -> This document is a work in progress and is intended to become an updated version of the Azure REST API guidelines. It is based on the best practices for building REST APIs, the existing Azure API Guidelines, and feedback from the API Stewardship Board. Your thoughts, comments, pull requests, and all other forms of feedback are welcomed and encouraged. If you have any questions, please reach out to @Mark Weitzel. +> This document is a work in progress and is intended to become an updated version of the Azure REST API guidelines. It is based on the best practices for building REST APIs, the existing Microsoft and Azure API Guidelines, and feedback from the API Stewardship Board. Your thoughts, comments, issues, pull requests, and all other forms of feedback are welcomed and encouraged. You can also reach out to Mark Weitzel as well. > > Thanks! From 3f32a365a57fc07aba4b8e058a5720bfb6d19eda Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Thu, 27 May 2021 19:42:11 -0400 Subject: [PATCH 086/729] experimenting w/prescriptive format & cut verbiage --- azure/Guidelines.md | 133 ++++++++++++++++++++------------------------ 1 file changed, 61 insertions(+), 72 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a0482346..3cd41169 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -20,28 +20,16 @@ | 2021-May-24 | WIP | This workstream opened to update and revise the guidelines| -TODO: Add/expand section on using these guidelines for building general APIs. MS customers can use these to build their own services. - - ## Introduction -The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. While these guidelines represent and codify many years of experience building high performant, scalable cloud services on Azure, they are generally applicable to all APIs. Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. We welcome and encourage new ideas, input and discussion. - -TODO: Add sentence on how to contribute, e.g. PRs, GH discussions, etc. - -Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. -> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. - - +The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. While these guidelines represent and codify many years of experience building high performant, scalable cloud services on Azure, they are generally applicable to all APIs. Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. ### Guideline Organization The Guiding Principles section presents the high level concerns that affect all Azure services. Adherence to these principles creates consistent design, makes it easier for developers to use your service, and reduces the learning curve for other Azure services. All of these are critical to creating a delightful experience for Azure developers. -These guidelines are organized in three primary sections; Guiding Principles, Building Blocks, and Common Patterns. The Guiding Principles section will present the set of considerations that impact the overall design of your API, e.g. naming, . - -Each section builds upon the other. For example, when updating resource collections, you should make sure to understand the difference in the HTTP verbs PUT and PATCH, as their behavior is quite different and directly affects how you expose the capability of your service. +These guidelines are organized in three primary sections; Advice for new services, Building Blocks, and Common API patterns. Each section builds upon the other. For example, when updating resource collections, you should make sure to understand the difference in the HTTP verbs PUT and PATCH, as their behavior is quite different and directly affects how you expose the capability of your service. #### Prescriptive Guidance This document will be as prescritive as possible. Specific guideance is labelled and color-coded to show the relative importance. In order from highest importance to lowest importance: @@ -59,92 +47,76 @@ __MAY__ consider this guideline if appropriate to your situation. If you feel you need an exception, or need clarity based on your situation, please engage with the [API Stewardship Board] prior to release of your API. -## Guiding Principles +## Advice for new services Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, especially over time. This document provides Microsoft teams building Azure services with a set of guidelines that will help service teams build great APIs. The guidelines can be applied to create an API that is approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation, and enabling a broad audience of developers across multiple languages. ->Our goal is to create a developer friendly API where: -> * customer workloads __MUST__ never break -> * customers __MUST__ be able to adopt a new version of service or SDK w/out requiring code changes - Service teams should engage the API Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. +Our goal is to create a developer friendly API where: +* customer workloads __MUST__ never break +* customers __MUST__ be able to adopt a new version of service or SDK w/out requiring code changes + +> Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. +> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. + + ### Start with developer experience A great API starts with a well thought out and designed service. It is extremely difficult, if not impossible, to create an elegant API that will work well on top of a service that is poorly designed. For example, if during a user study during a preview, you discover that customers are struggling to use your API, e.g. they don't understand the abstraction layer, take the time to fix your service. This will benefit the developer and your team. For this reason, it's important that you put yourself in the developer's shoes and think deeply about how they will be using your API and your service. ->Think about the code that a customer will write both before and after the REST API call. How will a developer use this API in the canonical use case? -> * Service teams __SHOULD__ provide examples in multiple languages, and __SHOULD__ include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). +Think about the code that a customer will write both before and after the REST API call. +:white_check_mark: **DO** provide examples in multiple languages -### Focus on hero scenarios -We all want to get our service out into the wild as quickly as possible. We don't want to waste time and energy building things our customers never use, or that rare edge case that happens once in a blue moon. While it sounds obvious, approach the design from the customer to the service; not from service to the customer. +:white_check_mark: **DO** include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). + +### Focus on hero scenarios It is important to realize that writing an API is, in many cases, the easist part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documenation, and creation of client libraries and examples.Focusing on hero scenarios reduces development, support, and maintenace costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A tell tale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. Service teams: -> * __SHOULD__ define "hero scenarios" first, then the operations required, & then design the API -> * __SHOULD__ provide example code that demonstrates their "Hero Scenarios." -> * __SHOULD NOT__ add APIs for speculative features customers might want -### Reflect key concepts through naming -TODO: Intro sentence for context +:white_check_mark: **DO** define "hero scenarios" first, then the operations required, & then design the API -As you identify the tasks and activities that developers will accomplish using your service, it will be important to develop a vocabulary that intuitively reflects your core concepts. - * Start with the "things" your API manipulates, then think about the operations that a developer needs to do to these "things". - * Keep the verbs present-tense. Avoid the use of past or future tense in most cases. - * Avoid the use of generic names like "Object", "Job", "Task", "Operation" (for example - the list is not exhaustive). - * What happens to the names when the focus of the service expands? It may be worth starting with a less generic name to avoid a breaking change later on. - * -TODO: Pull in section from Heath's doc +:white_check_mark: **DO** provide example code that demonstrates their "Hero Scenarios." +:x: **DO NOT** add APIs for speculative features customers might want ### Start with your API definition -You don't build a house without a blueprint. Neither should you build your service without a well thought-out API definition. Understanding how your service will be used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It should be reflect the naming decisions and make it easy for developers to implement your hero scenarios. -> * All Services **MUST** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. -> * All Services __SHOULD__ describe their services using ADL *[LINK TO ADL HERE]*. -> * ADL __SHOULD__ be used to generate the required OpenAPI Definition. +Understanding how your service will be used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It should be reflect the naming decisions and make it easy for developers to implement your hero scenarios. +:white_check_mark: **DO** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. +:ballot_box_with_check: **YOU SHOULD** describe their services using ADL *[LINK TO ADL HERE]*. + +:ballot_box_with_check: ** YOU SHOULD** use ADL to generate the required OpenAPI Definition. ### Use previews to iterate - Before releasing your API, plan to invest significant design effort, get customer feedback, & iterate through multiple previews. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. + Before releasing your API, plan to invest significant design effort, get customer feedback, & iterate through multiple previews. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. ->Use previews to get the shape right. There are different notification, breaking change, and lifetime requirements on preview API versions. -> * Service teams __SHOULD__ release and evaluate a minimum of 2 preview versions prior to the first GA release. -> * Service teams __SHOULD__ create feedback loops that actively solicit feedback from preview customers. +:ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. +:ballot_box_with_check: **YOU SHOULD** create feedback loops that actively solicit feedback from preview customers. -TODO: Provide references on how to run an effective preview ### Avoid surprises A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. -* Avoid polymorphism. An endpoint should work with a single type to avoid problems during SDK creation. Remember that a change to the model is a breaking change. -* Make Collections easy to work with. Collections are a common source of review comments. It is important to handle them in a consistent manner within your service. - * A collection __SHOULD__ return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. - * A collection __SHOULD__ support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. -* Build in [idempotency](https://stripe.com/blog/idempotency) from the outset. In a distributed cloud, each HTTP call must be idempotent. In fact, the HTTP specification requires that GET, PUT, DELETE, and HEAD be idempotent. You must be resilient in the face of failure. Developers rely on idempotency to build fault-tolerant systems. - * Example: Allow the developer to use PUT or PATCH to create a resource with a user-specified name or ID. A developer will commonly want to download a specific resource by name or ID. This provides the developer with an idempotent mechanism for creating resources. +:ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint __SHOULD__ work with a single type to avoid problems during SDK creation. +:ballot_box_with_check: **YOU SHOULD** make Collections easy to work with. Collections are a common source of review comments. It is important to handle them in a consistent manner within your service. -### Design for Resiliancy -As you build out your service and API, there are a number of decisions that can be made up front that add resiliancy. Addressing these as early as possible will help you iterate faster and avoid breaking changes. -* Use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. -* Implement conditional requests early. This allows you to support concurrency, which tends to be a concern later on. -* If your API specifies access conditions to another resource: -** Think about how to represent that model polymorphically. For example, you may be using a SQL Azure connection now, but extend to Cosmos DB, Azure Data Lake, or Redis Cache later on. Think about how you can specify that resource in a non-breaking manner. -* Implement managed identity access controls for accessing the other resource. Do not accept connection strings as a method of specifying access permissions. +:ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. -TODO: I'd like to be much more prescriptive here. -* Be concerned about data widths of numeric types. Wider data types (e.g. 64-bit vs. 32-bit) are more future-proof. -* Think about how the interface will be represented by an SDK. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. +:ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. + + +### Design for Resiliancy +As you build out your service and API, there are a number of decisions that can be made up front that add resiliency. Addressing these as early as possible will help you iterate faster and avoid breaking changes. -### Avoid breaking changes -Innovation and design improvements to a service and its API are often the result of user studies and understanding how a service is used through telemetry. Well defined and manage previews enable rapid learning through a tight feedback loop. Being able to iterate quickly and incorporate these learnings can be a competitve differentor. However, as services mature, developers will rely more and more on the API, using them as a foundation for their own applications. For this reason, it is imperative to effectively manage change. This is especially true once a service has GA'd its API. The guidelines in this document have been designed in such a way as to help service teams balance innovation and stability. +:ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. -> Service teams __MUST__ follow the breaking change guidelines specified in this document. *[LINK TO SECTION]* +:ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. +:ballot_box_with_check: **YOU SHOULD** use wider data types (e.g. 64-bit vs. 32-bit) as they are more future-proof. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. -There are additional considerations for management APIs. Microsoft teams building this aspect of the service should refer to the following resources for supplemental guidelines: -* [Azure Resource Manager Wiki][2] (Microsoft only). -* Use [RPaaS](https://armwiki.azurewebsites.net/rpaas/overview.html) (Microsoft only) to implement the Azure Resource Provider. ## Building Blocks: HTTP, REST, & JSON @@ -158,7 +130,9 @@ Azure services will adhere to the HTTP specification, [RFC7231](https://tools.ie * Bodies #### URLs -A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. Becuase these will be used so heavily by developers, careful consideration should be taken when devising your structure. For these reasons, service providers __SHOULD__ keep URLs readable and if possible, avoid UUIDs & %-encoding (ex: Cádiz) +A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. Because these will be used so heavily by developers, careful consideration should be taken when devising your structure. + +Service providers __SHOULD__ keep URLs readable and if possible, avoid UUIDs & %-encoding (ex: Cádiz) In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services. Specifically: @@ -185,13 +159,16 @@ For Azure PaaS services like SQL Azure, Azure Storage, Caching, etc., the unit o ##### Additional URL considerations When services produce URLs in response headers or bodies, they **MUST** use a consistent form – either always a GUID for tenant identifier or always a single verified domain - regardless of the URL used to reach the resource. -It is common that resources will differ by case. In addition, case may also affect computed values. Therefore, a service URL must be case-sensitive (except for scheme/host). If case doesn't match what you expect, the request __MUST__ fail with the appropriate HTTP return code. Further, when you are returning information in a Response, services __MUST__ maintain and respect proper case values. +A service URL must be case-sensitive (except for scheme/host). If case doesn't match what you expect, the request __MUST__ fail with the appropriate HTTP return code. + +When returning information in a Response, services __MUST__ maintain and respect proper case values. -Logging of URLs is another common practice. We want to take every precaution to prevent the leakage of Personal Identifying Information (PII). Azure services __MUST NOT__ include Personal Identifying Information (PII) in the URL. +Azure services __MUST NOT__ include Personal Identifying Information (PII) in the URL. The Max length=2083 characters __MUST__ be observed. If a URL excedes this length, the service __MUST__ return a ```414-URI Too Long``` Legal characters for a URL ar: 0-9 A-Z a-z - . _ ~ / ? # [ ] @ ! $ & ' ( ) * + , ; = + Services __SHOULD__ reserve the following characters for use exclusive use: ```/ ? # [ ] @ ! $ & ' ( ) * + , ; =``` @@ -279,7 +256,7 @@ Last-Modified | Response | (RFC1123) [see Optimistic Concurrency](http://TODO:li #### HTTP methods & idempotency -[Idempotentency](https://www.lexico.com/en/definition/idempotent), or the ability for the state of resource to remain unchanged when the same operation is applied, is a fundamental property of resilient cloud services. Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. (See our guiding principle of "No surprises".) +Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. Service providers __MUST__ implement all operations idempotently and __MUST__ adhere to the return codes in the following table: Method | Description | Response Status Code @@ -288,8 +265,7 @@ GET | Read the resource | 200-OK DELETE | Remove the resource | 204-No Content; avoid 404-Not Found PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created PUT | Create/Replace the *whole* resource | 200-OK, 201-Created - -> * Service providers __MUST__ implement all operations idempotently. + > * Service providers __SHOULD__ avoid using POST unless it can be implemented idempotently. #### Additional References @@ -301,7 +277,20 @@ PUT | Create/Replace the *whole* resource | 200-OK, 201-Created ### REST +#### Process a PATCH/PUT request +If...| | Response Code +----|----|---- +PATCH PUT | Any JSON field name/value not known/valid | 422-Unprocessable Entity +PATCH PUT | Any Read field passed (client can't set Read fields) | 422-Unprocessable Entity +| **Resource does not exist** | +PATCH PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity +PATCH PUT | Create resource using Create/Update fields |201-Created +| **Resource already exists** | +PATCH | Any Create field doesn't match current value (allows retries) |409-Conflict +PATCH | Update resource using Update fields | 200-OK +PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity +PUT | Overwrite resource entirely using Create/Update fields | 200-OK #### Handling unknown properties or parameters From 5b8f58084ebafd4157b1b204103e89060a6a681b Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 28 May 2021 09:25:52 -0400 Subject: [PATCH 087/729] Expanded json section --- azure/Guidelines.md | 135 +++++++++++++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 40 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 3cd41169..08b5463f 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -32,19 +32,19 @@ The Guiding Principles section presents the high level concerns that affect all These guidelines are organized in three primary sections; Advice for new services, Building Blocks, and Common API patterns. Each section builds upon the other. For example, when updating resource collections, you should make sure to understand the difference in the HTTP verbs PUT and PATCH, as their behavior is quite different and directly affects how you expose the capability of your service. #### Prescriptive Guidance -This document will be as prescritive as possible. Specific guideance is labelled and color-coded to show the relative importance. In order from highest importance to lowest importance: +This document will be as prescriptive as possible. Specific guidance is labelled and color-coded to show the relative importance. In order from highest importance to lowest importance: -__MUST__ adopt this guideline or follow this pattern. +:white_check_mark: **DO** adopt this guideline or follow this pattern. If you feel you need an exception, engage with the Architecture Board prior to implementation. -__MUST NOT__ adopt this guideline or follow this pattern. +:no_entry: **DO NOT** follow this pattern. If you feel you need an exception, engage with the Architecture Board prior to implementation. -__SHOULD__ strongly consider this guideline. +:ballot_box_with_check: **YOU SHOULD** strongly consider this guideline. If not following this advice, you MUST disclose the variance during the Architecture Board design review. -__SHOULD NOT__ strongly consider this guideline. +:warning: **YOU SHOULD NOT** strongly consider avoiding the described pattern. If not following this advice, you MUST disclose the variance during the Architecture Board design review. -__MAY__ consider this guideline if appropriate to your situation. +:heavy_check_mark: **YOU MAY** consider this guideline if appropriate to your situation. No notification to the architecture board is required. - If you feel you need an exception, or need clarity based on your situation, please engage with the [API Stewardship Board] prior to release of your API. +*If you feel you need an exception, or need clarity based on your situation, please engage with the [API Stewardship Board] prior to release of your API.* ## Advice for new services @@ -55,8 +55,8 @@ This document provides Microsoft teams building Azure services with a set of gui Service teams should engage the API Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. Our goal is to create a developer friendly API where: -* customer workloads __MUST__ never break -* customers __MUST__ be able to adopt a new version of service or SDK w/out requiring code changes +:white_check_mark: **DO** ensure that customer workloads never break +:white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK w/out requiring code changes > Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. > A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. @@ -79,7 +79,7 @@ It is important to realize that writing an API is, in many cases, the easist par :white_check_mark: **DO** provide example code that demonstrates their "Hero Scenarios." -:x: **DO NOT** add APIs for speculative features customers might want +:no_entry: **DO NOT** add APIs for speculative features customers might want ### Start with your API definition Understanding how your service will be used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It should be reflect the naming decisions and make it easy for developers to implement your hero scenarios. @@ -87,7 +87,7 @@ Understanding how your service will be used and defining its model and interacti :ballot_box_with_check: **YOU SHOULD** describe their services using ADL *[LINK TO ADL HERE]*. -:ballot_box_with_check: ** YOU SHOULD** use ADL to generate the required OpenAPI Definition. +:ballot_box_with_check: **YOU SHOULD** use ADL to generate the required OpenAPI Definition. ### Use previews to iterate Before releasing your API, plan to invest significant design effort, get customer feedback, & iterate through multiple previews. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. @@ -132,11 +132,11 @@ Azure services will adhere to the HTTP specification, [RFC7231](https://tools.ie #### URLs A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. Because these will be used so heavily by developers, careful consideration should be taken when devising your structure. -Service providers __SHOULD__ keep URLs readable and if possible, avoid UUIDs & %-encoding (ex: Cádiz) +:ballot_box_with_check: **YOU SHOULD** keep URLs readable and if possible, avoid UUIDs & %-encoding (ex: Cádiz) In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services. Specifically: -All services **MUST** expose their service to developers via the following URL pattern: +:white_check_mark: **DO** expose their service to developers via the following URL pattern: ```text https://.// @@ -169,7 +169,7 @@ The Max length=2083 characters __MUST__ be observed. If a URL excedes this lengt Legal characters for a URL ar: 0-9 A-Z a-z - . _ ~ / ? # [ ] @ ! $ & ' ( ) * + , ; = -Services __SHOULD__ reserve the following characters for use exclusive use: ```/ ? # [ ] @ ! $ & ' ( ) * + , ; =``` +:ballot_box_with_check: **YOU SHOULD** reserve the following characters for use exclusive use: ```/ ? # [ ] @ ! $ & ' ( ) * + , ; =``` ### Direct endpoint URLs @@ -204,12 +204,13 @@ https://-..azure.net ### HTTP Request / Response Pattern -The HTTP Request / Response pattern will dictate much of how your API behaves, for example; POST methods must be idempotent, GET methods may be cached, the If-Modified and etag headers determine your optimistic concurrency strategy. The URL of a service, along with its request / response, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern __SHOULD__ be one of the first implementation decisions you will make. For each request / response, the service: +The HTTP Request / Response pattern will dictate much of how your API behaves, for example; POST methods must be idempotent, GET methods may be cached, the If-Modified and etag headers determine your optimistic concurrency strategy. The URL of a service, along with its request / response, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you will make. For each request / response, the service: -> * __MUST__ validate all inputs to a request. -> * __SHOULD__ return the same object that was sent to the API in the response for all create or upsert operations. +:white_check_mark: **DO** validate all inputs to a request. +:white_check_mark: **DO** return the same object that was sent to the API in the response for all create or upsert operations. -Because beacuse information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. Service provides __MUST__ use the following table when translating strings: +Because beacuse information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. +:white_check_mark: **DO** use the following table when translating strings: Data type | Document string must be -------- | ------- @@ -225,11 +226,15 @@ Byte array | Base-64 encoded, max length TODO: Expand the explanation for numbers. #### Common Request & Response Headers -The table below lists the request / response headers most used by Azure services Service providers __SHOULD__ properly handle all headers annotated in *italics*. In addition, each request / response header: -> * __MUST__ be specificed using kabob-style-text -> * __MUST__ be all lowercase -> * __SHOULD NOT__ use "x-" prefix, unless already existing in production +The table below lists the request / response headers most used by Azure services Service providers. +:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. In addition, each request / response header: + +:white_check_mark: **DO** specify headers using kabob-style-text + +:white_check_mark: **DO** use all lowercase for headers + +:no_entry: **DO NOT** use "x-" prefix for headers, unless the header already exists in production Header Key | Applies to | Example ------------ | ------------- | ------------- @@ -256,8 +261,11 @@ Last-Modified | Response | (RFC1123) [see Optimistic Concurrency](http://TODO:li #### HTTP methods & idempotency -Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. Service providers __MUST__ implement all operations idempotently and __MUST__ adhere to the return codes in the following table: +Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. + +:white_check_mark: **DO** implement all operations idempotently, ideally from the outset. +:white_check_mark: **DO** adhere to the return codes in the following table when implementing your API: Method | Description | Response Status Code ----|----|---- @@ -266,7 +274,7 @@ DELETE | Remove the resource | 204-No Content; avoid 404-Not Found PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created PUT | Create/Replace the *whole* resource | 200-OK, 201-Created -> * Service providers __SHOULD__ avoid using POST unless it can be implemented idempotently. +:warning: **YOU SHOULD NOT** using the POST method unless you can guarantee it can be implemented idempotently. #### Additional References * [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) @@ -276,27 +284,78 @@ PUT | Create/Replace the *whole* resource | 200-OK, 201-Created ### REST +REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment,reduced latency via caching, and security. When applying REST to your API, you will define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's URLs determine the hierarchical path developers use to retrieve and update the state of your resource. Note, it's important to model resource state, not to behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. +TODO: Add link to behavior section + +When designing your service, it is important to optimize for the developer using your API. + +:white_check_mark: **DO** focus heavily on great & consistent naming + +:white_check_mark: **DO** ensure your resource paths make sense + +:white_check_mark: **DO** simplify call with few required query parameters & JSON fields + +:white_check_mark: **DO** establish clear contracts for string values + +:white_check_mark: **DO** use proper response codes/payloads so customer can self-fix + + +#### JSON resource schema & field mutability +For a given URL path, the JSON schema (data type) should be the same for PATCH, PUT, GET, DELETE, and GETting collection items. This allows one SDK type for input/output operations and enables the response to be passed back in request. While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. Tools like ADL do allow annotation of mutability, enabling more sophisticated code generation of client libraries. + +:white_check_mark: **DO** create a model of your data types. For each field, apply one of the following rules: + +Field Mutability | Service Request's behavior for this bield +----| ---- +**Create** | Service honors field only when creating a resource  Minimize create-only fields so customers don't have to delete & re-create the resource +**Update** | Service honors field when creating or updating a resource +**Read** |Service fails request (or accept if they match what's in the resource); returns these fields in a response + +#### General guidelines +The following are general guidelines when using REST. + +:white_check_mark: **DO** use GET with JSON in response body + +:white_check_mark: **DO** create and update resource using PATCH [RFC5789] with JSON Merge Patch request body + +:white_check_mark: **DO** use PUT with JSON for wholesale create/update update operations. Take special care to hand versioning issues properly. + +:white_check_mark: **DO** use DELETE when removing resources +* NOTE: Ids are "Customer Content" & Azure allows their use + +:white_check_mark: **DO** make the payloads for PUT, PATCH, GET the same + +:white_check_mark: **DO** make fields simple + +:white_check_mark: **DO** preserve string casing/array order + +:no_entry: **DO NOT** let an operation succeeed if unknown fields or bad values are passed + +:no_entry: **DO NOT** return secret fields via GET +* Ex: do not return adminPassword in JSON. + +:heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary** + #### Process a PATCH/PUT request +PATCH/PUT requests accept a subset of fields. Because of this, they require additional guidelines handling requests and responses. In general, you want to avoid creating partial resources as a result of create operations. + +:white_check_mark: **DO** adhere to the return codes in the following table when implementing your API. These tests be processed in this oder: -If...| | Response Code +When using this method |if this condition happens | use this response code ----|----|---- -PATCH PUT | Any JSON field name/value not known/valid | 422-Unprocessable Entity -PATCH PUT | Any Read field passed (client can't set Read fields) | 422-Unprocessable Entity -| **Resource does not exist** | -PATCH PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity -PATCH PUT | Create resource using Create/Update fields |201-Created -| **Resource already exists** | +PATCH/PUT | Any JSON field name/value not known/valid | 422-Unprocessable Entity +PATCH/PUT | Any Read field passed (client can't set Read fields) | 422-Unprocessable Entity +| **IF the resource does not exist** | +PATCH/PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity +PATCH/PUT | Create resource using Create/Update fields |201-Created +| **If the resource already exists** | PATCH | Any Create field doesn't match current value (allows retries) |409-Conflict PATCH | Update resource using Update fields | 200-OK PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity PUT | Overwrite resource entirely using Create/Update fields | 200-OK - -#### Handling unknown properties or parameters -This should dovetail nicely into the added guidance on how to deal with "readOnly" values that a client may (incorrectly) supply in a request. - - +#### Handling Errors ### JSON @@ -328,7 +387,3 @@ This should dovetail nicely into the added guidance on how to deal with "readOnl * Reach out and engage the stewardship team! -## API Guidelines Quick Reference Sheet -TODO: Should we create a quick reference sheet?? -Add the Must / Must NOT w/links -See if we can generate this \ No newline at end of file From 245a78f219651db39458cd4dd7b35860cd6709c8 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 28 May 2021 09:30:24 -0400 Subject: [PATCH 088/729] Added placeholder for error handlilng #231 --- azure/Guidelines.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 08b5463f..f665db9c 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -357,6 +357,11 @@ PUT | Overwrite resource entirely using Create/Update fields | 200-OK #### Handling Errors +:white_check_mark: **DO** deturn x-ms-error-code header with string + +:white_check_mark: **DO** ensure your service returns the error response body + + ### JSON ## Common API Patterns From eb1b209782f7a4860f9031cccab9dfd31bef63f8 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 28 May 2021 09:50:08 -0400 Subject: [PATCH 089/729] Minor formatting and cleanup. #231 --- azure/Guidelines.md | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index f665db9c..6493b5d6 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -55,7 +55,9 @@ This document provides Microsoft teams building Azure services with a set of gui Service teams should engage the API Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. Our goal is to create a developer friendly API where: + :white_check_mark: **DO** ensure that customer workloads never break + :white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK w/out requiring code changes > Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. @@ -130,6 +132,8 @@ Azure services will adhere to the HTTP specification, [RFC7231](https://tools.ie * Bodies #### URLs +TODO: Update this section + A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. Because these will be used so heavily by developers, careful consideration should be taken when devising your structure. :ballot_box_with_check: **YOU SHOULD** keep URLs readable and if possible, avoid UUIDs & %-encoding (ex: Cádiz) @@ -207,9 +211,11 @@ https://-..azure.net The HTTP Request / Response pattern will dictate much of how your API behaves, for example; POST methods must be idempotent, GET methods may be cached, the If-Modified and etag headers determine your optimistic concurrency strategy. The URL of a service, along with its request / response, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you will make. For each request / response, the service: :white_check_mark: **DO** validate all inputs to a request. + :white_check_mark: **DO** return the same object that was sent to the API in the response for all create or upsert operations. Because beacuse information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. + :white_check_mark: **DO** use the following table when translating strings: Data type | Document string must be @@ -228,13 +234,13 @@ Byte array | Base-64 encoded, max length #### Common Request & Response Headers The table below lists the request / response headers most used by Azure services Service providers. -:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. In addition, each request / response header: +:white_check_mark: **DO** specify headers using kabob-style-text. -:white_check_mark: **DO** specify headers using kabob-style-text +:white_check_mark: **DO** use all lowercase for headers. -:white_check_mark: **DO** use all lowercase for headers +:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. In addition, each request / response header. -:no_entry: **DO NOT** use "x-" prefix for headers, unless the header already exists in production +:no_entry: **DO NOT** use "x-" prefix for headers, unless the header already exists in production. Header Key | Applies to | Example ------------ | ------------- | ------------- @@ -265,6 +271,8 @@ Implementing services in an idempotent manner, with an "exactly once" semantic, :white_check_mark: **DO** implement all operations idempotently, ideally from the outset. +:warning: **YOU SHOULD NOT** using the POST method unless you can guarantee it can be implemented idempotently. + :white_check_mark: **DO** adhere to the return codes in the following table when implementing your API: Method | Description | Response Status Code @@ -274,7 +282,7 @@ DELETE | Remove the resource | 204-No Content; avoid 404-Not Found PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created PUT | Create/Replace the *whole* resource | 200-OK, 201-Created -:warning: **YOU SHOULD NOT** using the POST method unless you can guarantee it can be implemented idempotently. + #### Additional References * [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) @@ -312,29 +320,27 @@ Field Mutability | Service Request's behavior for this bield **Read** |Service fails request (or accept if they match what's in the resource); returns these fields in a response #### General guidelines -The following are general guidelines when using REST. +The following are general guidelines when using REST: -:white_check_mark: **DO** use GET with JSON in response body +:white_check_mark: **DO** use GET with JSON in response body. -:white_check_mark: **DO** create and update resource using PATCH [RFC5789] with JSON Merge Patch request body +:white_check_mark: **DO** create and update resource using PATCH [RFC5789] with JSON Merge Patch request body. :white_check_mark: **DO** use PUT with JSON for wholesale create/update update operations. Take special care to hand versioning issues properly. -:white_check_mark: **DO** use DELETE when removing resources -* NOTE: Ids are "Customer Content" & Azure allows their use +:white_check_mark: **DO** use DELETE when removing resources. NOTE: Ids are "Customer Content" & Azure allows their use. -:white_check_mark: **DO** make the payloads for PUT, PATCH, GET the same +:white_check_mark: **DO** make the payloads for PUT, PATCH, GET the same. -:white_check_mark: **DO** make fields simple +:white_check_mark: **DO** make fields simple. -:white_check_mark: **DO** preserve string casing/array order +:white_check_mark: **DO** preserve string casing/array order. -:no_entry: **DO NOT** let an operation succeeed if unknown fields or bad values are passed +:no_entry: **DO NOT** let an operation succeeed if unknown fields or bad values are passed. -:no_entry: **DO NOT** return secret fields via GET -* Ex: do not return adminPassword in JSON. +:no_entry: **DO NOT** return secret fields via GET. For exampple, do not return adminPassword in JSON. -:heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary** +:heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. #### Process a PATCH/PUT request From 9b05f04b0c20950fa33ffb726dec9fe18b268457 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 28 May 2021 13:41:06 -0400 Subject: [PATCH 090/729] Initial pass, JSON #231 --- azure/Guidelines.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 6493b5d6..63aa91f3 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -369,11 +369,51 @@ PUT | Overwrite resource entirely using Create/Update fields | 200-OK ### JSON +Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: +* **Boolean:** true/false +* **Number:** signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) +* **String:** used for everything else + +:white_check_mark: **DO** use integers within the acceptable range of JSON number. + +#### String contracts +When using strings, you must establish, and adhere to, a well defined contract for the format. For example, you should be cognizant of attributes like maximum length, legal characters, case-sensitivity, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. + +:white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable." +:white_check_mark: **DO** use [RFC3339] for date/time. +:white_check_mark: **DO** use [RFC4122] for UUIDs. + +##### Composite types +JSON also supports composing strings into higher order constructs, for example: +* **Object**: { "name" : value, … } +* **Array**: [ value, … ] + + +:warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with updates when using ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. + +:ballot_box_with_check: **YOU SHOULD** use Maps instead of Arrays. + + +#### Enums & SDKs (Client libraries) + + + + + +:white_check_mark: **DO** +:no_entry: **DO NOT** +:ballot_box_with_check: **YOU SHOULD** +:warning: **YOU SHOULD NOT** +:heavy_check_mark: **YOU MAY** + + ## Common API Patterns ### Performing an Action +### Conditional Access + ### Collections ### Long Running Operations From 66be60d661777a35101bf0d3efff7a974dadc966 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 28 May 2021 14:44:50 -0400 Subject: [PATCH 091/729] first pass - enum section #231 --- azure/Guidelines.md | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 63aa91f3..eb3f9625 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -396,16 +396,40 @@ JSON also supports composing strings into higher order constructs, for example: #### Enums & SDKs (Client libraries) +It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI specification as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation. However, your services will have client libraries in many different programming languages. And because enumerations are handled differently depending on the language, this can lead to significant interoperability issues. +To address these issues, Microsoft's tooling uses the concept of an "extensible enum," which effectively treats all enumerations as strings. In addition, "extensible enums" indicate to client libraries that the list of values is only a *partial* list. This enables the set of values to grow over time while ensuring stability in client libraries. +:white_check_mark: **DO** use "extensible enums" +:ballot_box_with_check: **YOU SHOULD** be prepared to handle new values in your client -:white_check_mark: **DO** -:no_entry: **DO NOT** -:ballot_box_with_check: **YOU SHOULD** -:warning: **YOU SHOULD NOT** -:heavy_check_mark: **YOU MAY** +:no_entry: **DO NOT** send "enum integers" over the wire. +:no_entry: **DO NOT** remove items from your enumerated list. This will likely result in a breaking change to client libraries. + +#### Discriminate polymorphic types +While polymorphism is a powerful concept in programming languages, returing "polymorphic JSON" as part of an API introduces significant complexity for implementors of client libraries and developers, especially as new versions of your service are introduced. For example, consider a service where V1 introduces two shapes, Retangles and Circles. They could be represented in JSON as follows: + +**Rectangle** +```json +{"kind": "rectangle", +"x": 100, "y": 50, "width": 10, "length": 24, "fillColor": "Red", "lineColor": "White", +"subscription": {"expiration": "2024" "kind": "free"}} +``` + +**Circle** + ```json + {"kind": "circle", +"x": 100, "y": 50, "radius": 10, "fillColor": "Green", "lineColor": "Black", +"subscription": { "expiration": "2024", "kind": "paid", "invoice": "123456"}}`` +``` + +The first issue is that developers writing code against this JSON string contract will have a very difficult time, especially in typed languages. It will be impossible to determine what the actual type is during development, minimizing the effectiveness of tooling. At runtime, developers will have to parse the JSON, interpret the "kind" value, and *then* cast to the proper sub-class. + +Overall, this is a very brittle design that leads to a poor developer experience, especially over time. Consider the scenario when a new shape is introduced in V2 of the API. Existing client libraries that work with V1 will have no concept of this new shape and, when receiving an unknown shape, fail. + +:warning: **YOU SHOULD NOT** use polymorphic types. Instead, return discriminate types. ## Common API Patterns From 153484068cdb18d2613217ec95ce125bbbf1776a Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 28 May 2021 21:29:46 +0000 Subject: [PATCH 092/729] enhance preview related guidance --- azure/Guidelines.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index eb3f9625..cb48a11c 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -87,7 +87,7 @@ It is important to realize that writing an API is, in many cases, the easist par Understanding how your service will be used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It should be reflect the naming decisions and make it easy for developers to implement your hero scenarios. :white_check_mark: **DO** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. -:ballot_box_with_check: **YOU SHOULD** describe their services using ADL *[LINK TO ADL HERE]*. +:ballot_box_with_check: **YOU SHOULD** describe their servicesgi using ADL *[LINK TO ADL HERE]*. :ballot_box_with_check: **YOU SHOULD** use ADL to generate the required OpenAPI Definition. @@ -95,8 +95,9 @@ Understanding how your service will be used and defining its model and interacti Before releasing your API, plan to invest significant design effort, get customer feedback, & iterate through multiple previews. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. :ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. -:ballot_box_with_check: **YOU SHOULD** create feedback loops that actively solicit feedback from preview customers. - +:ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. +:ballot_box_with_check: **YOU SHOULD** consider doing a *code with* exercise in which you actively develop with the customer, observing and learning from their API usage. +:ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. ### Avoid surprises A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. From 43b84172c2c7c9caf713e28c6750cc98abfccbf0 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 28 May 2021 21:33:56 +0000 Subject: [PATCH 093/729] enhance preview related guidance --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index cb48a11c..55369e68 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -87,7 +87,7 @@ It is important to realize that writing an API is, in many cases, the easist par Understanding how your service will be used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It should be reflect the naming decisions and make it easy for developers to implement your hero scenarios. :white_check_mark: **DO** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. -:ballot_box_with_check: **YOU SHOULD** describe their servicesgi using ADL *[LINK TO ADL HERE]*. +:ballot_box_with_check: **YOU SHOULD** describe their services using ADL *[LINK TO ADL HERE]*. :ballot_box_with_check: **YOU SHOULD** use ADL to generate the required OpenAPI Definition. From 8a3f553b5131db347b4105b798ab46ed8f3c25be Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 28 May 2021 21:43:53 +0000 Subject: [PATCH 094/729] minor edit on resiliency --- azure/Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 55369e68..594d8c4b 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -111,8 +111,8 @@ A major inhibitor to adoption and usage is when an API behaves in an unexpected :ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. -### Design for Resiliancy -As you build out your service and API, there are a number of decisions that can be made up front that add resiliency. Addressing these as early as possible will help you iterate faster and avoid breaking changes. +### Design for Change Resiliancy +As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. :ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. From 55e48b7a4bd49dfcdf7f9ab702aa66d4933b58f9 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 4 Jun 2021 15:23:46 +0000 Subject: [PATCH 095/729] editing for clarity/syntax/spelling/extraneous whitespace/a few substantive changes --- azure/Guidelines.md | 151 +++++++++++++++++++------------------------- 1 file changed, 66 insertions(+), 85 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 594d8c4b..be18e8c5 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -23,8 +23,7 @@ ## Introduction -The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. While these guidelines represent and codify many years of experience building high performant, scalable cloud services on Azure, they are generally applicable to all APIs. Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. - +The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. While these guidelines represent and codify many years of experience building high performant, scalable cloud services on Azure, they are generally applicable to all REST APIs. Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. ### Guideline Organization The Guiding Principles section presents the high level concerns that affect all Azure services. Adherence to these principles creates consistent design, makes it easier for developers to use your service, and reduces the learning curve for other Azure services. All of these are critical to creating a delightful experience for Azure developers. @@ -50,24 +49,25 @@ This document will be as prescriptive as possible. Specific guidance is labelled ## Advice for new services Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, especially over time. -This document provides Microsoft teams building Azure services with a set of guidelines that will help service teams build great APIs. The guidelines can be applied to create an API that is approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation, and enabling a broad audience of developers across multiple languages. +This document provides Microsoft teams building Azure services with a set of guidelines that will help service teams build great APIs. The guidelines can be applied to create an API that is approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation, and enabling a broad audience of developers across multiple languages. -Service teams should engage the API Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. +Azure Service teams should engage the API Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. -Our goal is to create a developer friendly API where: +Your goal is to create a developer friendly API where: :white_check_mark: **DO** ensure that customer workloads never break :white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK w/out requiring code changes +## Azure Management Plane vs Data Plane > Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. -> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. +> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on the conventions of the Azure ARM. ### Start with developer experience -A great API starts with a well thought out and designed service. It is extremely difficult, if not impossible, to create an elegant API that will work well on top of a service that is poorly designed. For example, if during a user study during a preview, you discover that customers are struggling to use your API, e.g. they don't understand the abstraction layer, take the time to fix your service. This will benefit the developer and your team. For this reason, it's important that you put yourself in the developer's shoes and think deeply about how they will be using your API and your service. +A great API starts with a well thought out and designed service. It is extremely difficult to create an elegant API that will work well on top of a service that is poorly designed. It is important that your development team builds some client code using the API. Hold reviews and share what is learend with your team. Engage with your customers during a preview release. If during a preview you discover that customers are struggling to use your API, e.g. they don't understand the abstraction layer, take the time to fix your service abstractions. This will benefit the developer and your team. Put yourself in the developer's shoes and think deeply about how they will be using your API and your service. -Think about the code that a customer will write both before and after the REST API call. +Think about the code that a customer will write both before and after the REST API call. What data structures will they need to assemble? What is the most likely next call? :white_check_mark: **DO** provide examples in multiple languages @@ -85,32 +85,31 @@ It is important to realize that writing an API is, in many cases, the easist par ### Start with your API definition Understanding how your service will be used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It should be reflect the naming decisions and make it easy for developers to implement your hero scenarios. -:white_check_mark: **DO** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes their service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. +:white_check_mark: **DO** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes the service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. -:ballot_box_with_check: **YOU SHOULD** describe their services using ADL *[LINK TO ADL HERE]*. +:ballot_box_with_check: **YOU SHOULD** describe their services using ADL *[LINK TO ADL HERE]*. -:ballot_box_with_check: **YOU SHOULD** use ADL to generate the required OpenAPI Definition. +:ballot_box_with_check: **YOU SHOULD** use ADL to generate the required OpenAPI Definition. ### Use previews to iterate - Before releasing your API, plan to invest significant design effort, get customer feedback, & iterate through multiple previews. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. + Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. -:ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. +:ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. :ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. :ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. :ballot_box_with_check: **YOU SHOULD** consider doing a *code with* exercise in which you actively develop with the customer, observing and learning from their API usage. -:ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. +:ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. ### Avoid surprises -A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. +A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. :ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint __SHOULD__ work with a single type to avoid problems during SDK creation. -:ballot_box_with_check: **YOU SHOULD** make Collections easy to work with. Collections are a common source of review comments. It is important to handle them in a consistent manner within your service. +:ballot_box_with_check: **YOU SHOULD** make [Collections](#Collections) easy to work with. Collections are a common source of review comments. It is important to handle them in a consistent manner within your service. :ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. :ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. - ### Design for Change Resiliancy As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. @@ -120,26 +119,24 @@ As you build out your service and API, there are a number of decisions that can :ballot_box_with_check: **YOU SHOULD** use wider data types (e.g. 64-bit vs. 32-bit) as they are more future-proof. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. - - ## Building Blocks: HTTP, REST, & JSON -The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet, namely HTTP, REST, and JSON. This section will provide you with a general understanding of how these technologies should be applied when creating your service. +The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet, namely HTTP, REST, and JSON. This section will provide you with a general understanding of how these technologies should be applied when creating your service. ### HTTP Azure services will adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231), as closely possible when presenting their API. This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: -* Uniform Resource Locators (URLs) -* HTTP Methods +* [Uniform Resource Locators (URLs)](URLS) +* HTTP Methods * Headers * Bodies #### URLs TODO: Update this section -A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. Because these will be used so heavily by developers, careful consideration should be taken when devising your structure. +A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. These are so central to the developer experience that careful consideration should be given when devising your URL structure. :ballot_box_with_check: **YOU SHOULD** keep URLs readable and if possible, avoid UUIDs & %-encoding (ex: Cádiz) -In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services. Specifically: +In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services. Specifically: :white_check_mark: **DO** expose their service to developers via the following URL pattern: @@ -164,9 +161,9 @@ For Azure PaaS services like SQL Azure, Azure Storage, Caching, etc., the unit o ##### Additional URL considerations When services produce URLs in response headers or bodies, they **MUST** use a consistent form – either always a GUID for tenant identifier or always a single verified domain - regardless of the URL used to reach the resource. -A service URL must be case-sensitive (except for scheme/host). If case doesn't match what you expect, the request __MUST__ fail with the appropriate HTTP return code. +A service URL must be case-sensitive (except for scheme/host). If case doesn't match what you expect, the request __MUST__ fail with the appropriate HTTP return code. -When returning information in a Response, services __MUST__ maintain and respect proper case values. +When returning information in a Response, services __MUST__ maintain and respect proper case values. Azure services __MUST NOT__ include Personal Identifying Information (PII) in the URL. @@ -176,7 +173,6 @@ Legal characters for a URL ar: 0-9 A-Z a-z - . _ ~ / ? # [ ] @ ! $ :ballot_box_with_check: **YOU SHOULD** reserve the following characters for use exclusive use: ```/ ? # [ ] @ ! $ & ' ( ) * + , ; =``` - ### Direct endpoint URLs In addition to the required format above, services **MAY** also choose to expose direct endpoint for performance or routing reasons. The direct endpoint should be discoverable by clients, to ensure that developers are presented with a consistent pattern for accessing Azure services. @@ -207,7 +203,6 @@ https://-..azure.net Content-Location: https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 ``` - ### HTTP Request / Response Pattern The HTTP Request / Response pattern will dictate much of how your API behaves, for example; POST methods must be idempotent, GET methods may be cached, the If-Modified and etag headers determine your optimistic concurrency strategy. The URL of a service, along with its request / response, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you will make. For each request / response, the service: @@ -239,13 +234,13 @@ The table below lists the request / response headers most used by Azure services :white_check_mark: **DO** use all lowercase for headers. -:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. In addition, each request / response header. +:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. In addition, each request / response header. :no_entry: **DO NOT** use "x-" prefix for headers, unless the header already exists in production. -Header Key | Applies to | Example +Header Key | Applies to | Example ------------ | ------------- | ------------- -*authorization* | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) +*authorization* | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) *x-ms-useragent* | Request | [see Telemetry](http://TODO:link-goes-here) traceparent | Request | [see Distributed Tracing]](http://TODO:link-goes-here) tracecontext | Request | [see Distributed Tracing](http://TODO:link-goes-here) @@ -263,42 +258,37 @@ retry-after | Response | 180 [see Throttling Client Requests] *x-ms-error-code* | Response | [see Processing a REST Request](http://TODO:link-goes-here) Last-Modified | Response | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) - -TODO: Fix the links. +TODO: Fix the links. -#### HTTP methods & idempotency -Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. +#### HTTP Methods & Idempotency +Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. :white_check_mark: **DO** implement all operations idempotently, ideally from the outset. -:warning: **YOU SHOULD NOT** using the POST method unless you can guarantee it can be implemented idempotently. +:warning: **YOU SHOULD NOT** using the POST method unless you can guarantee it can be implemented idempotently. :white_check_mark: **DO** adhere to the return codes in the following table when implementing your API: -Method | Description | Response Status Code +Method | Description | Response Status Code ----|----|---- -GET | Read the resource | 200-OK -DELETE | Remove the resource | 204-No Content; avoid 404-Not Found +GET | Read the resource | 200-OK +DELETE | Remove the resource | 204-No Content; avoid 404-Not Found PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created -PUT | Create/Replace the *whole* resource | 200-OK, 201-Created - - +PUT | Create/Replace the *whole* resource | 200-OK, 201-Created #### Additional References * [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) * [Standard HTTP Headers](https://httpwg.org/specs/rfc7231.html#header.field.registration) * [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) - - ### REST -REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment,reduced latency via caching, and security. When applying REST to your API, you will define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's URLs determine the hierarchical path developers use to retrieve and update the state of your resource. Note, it's important to model resource state, not to behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. +REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you will define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to retrieve and update the state of your resource. Note, it's important to model resource state, not to behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. TODO: Add link to behavior section When designing your service, it is important to optimize for the developer using your API. -:white_check_mark: **DO** focus heavily on great & consistent naming +:white_check_mark: **DO** focus heavily on clear & consistent naming :white_check_mark: **DO** ensure your resource paths make sense @@ -308,24 +298,23 @@ When designing your service, it is important to optimize for the developer using :white_check_mark: **DO** use proper response codes/payloads so customer can self-fix - -#### JSON resource schema & field mutability -For a given URL path, the JSON schema (data type) should be the same for PATCH, PUT, GET, DELETE, and GETting collection items. This allows one SDK type for input/output operations and enables the response to be passed back in request. While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. Tools like ADL do allow annotation of mutability, enabling more sophisticated code generation of client libraries. +#### JSON Resource Schema & Field Mutability +For a given URL path, the JSON schema (data type) should be the same for PATCH, PUT, GET, DELETE, and GETting collection items. This allows one SDK type for input/output operations and enables the response to be passed back in request. While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. :white_check_mark: **DO** create a model of your data types. For each field, apply one of the following rules: -Field Mutability | Service Request's behavior for this bield +Field Mutability | Service Request's behavior for this field ----| ---- -**Create** | Service honors field only when creating a resource  Minimize create-only fields so customers don't have to delete & re-create the resource +**Create** | Service honors field only when creating a resource *Note: Minimize create-only fields so customers don't have to delete & re-create the resource.* **Update** | Service honors field when creating or updating a resource -**Read** |Service fails request (or accept if they match what's in the resource); returns these fields in a response +**Read** |Service fails request (or 'Accept' if it matches what's in the resource); returns these symmetric fields in a response #### General guidelines The following are general guidelines when using REST: -:white_check_mark: **DO** use GET with JSON in response body. +:white_check_mark: **DO** serve GET for resource retrieval and send JSON in the response body. -:white_check_mark: **DO** create and update resource using PATCH [RFC5789] with JSON Merge Patch request body. +:white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. :white_check_mark: **DO** use PUT with JSON for wholesale create/update update operations. Take special care to hand versioning issues properly. @@ -339,10 +328,9 @@ The following are general guidelines when using REST: :no_entry: **DO NOT** let an operation succeeed if unknown fields or bad values are passed. -:no_entry: **DO NOT** return secret fields via GET. For exampple, do not return adminPassword in JSON. +:no_entry: **DO NOT** return secret fields via GET. For exampple, do not return adminPassword in JSON. -:heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. - +:heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. #### Process a PATCH/PUT request PATCH/PUT requests accept a subset of fields. Because of this, they require additional guidelines handling requests and responses. In general, you want to avoid creating partial resources as a result of create operations. @@ -352,15 +340,15 @@ PATCH/PUT requests accept a subset of fields. Because of this, they require addi When using this method |if this condition happens | use this response code ----|----|---- PATCH/PUT | Any JSON field name/value not known/valid | 422-Unprocessable Entity -PATCH/PUT | Any Read field passed (client can't set Read fields) | 422-Unprocessable Entity +PATCH/PUT | Any Read field passed (client can't set Read fields) | 422-Unprocessable Entity | **IF the resource does not exist** | -PATCH/PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity -PATCH/PUT | Create resource using Create/Update fields |201-Created +PATCH/PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity +PATCH/PUT | Create resource using Create/Update fields |201-Created | **If the resource already exists** | -PATCH | Any Create field doesn't match current value (allows retries) |409-Conflict +PATCH | Any Create field doesn't match current value (allows retries) |409-Conflict PATCH | Update resource using Update fields | 200-OK -PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity -PUT | Overwrite resource entirely using Create/Update fields | 200-OK +PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity +PUT | Overwrite resource entirely using Create/Update fields | 200-OK #### Handling Errors @@ -368,40 +356,37 @@ PUT | Overwrite resource entirely using Create/Update fields | 200-OK :white_check_mark: **DO** ensure your service returns the error response body - ### JSON Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: -* **Boolean:** true/false -* **Number:** signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) -* **String:** used for everything else +* **Boolean:** true/false +* **Number:** signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) +* **String:** used for everything else :white_check_mark: **DO** use integers within the acceptable range of JSON number. -#### String contracts +#### String Contracts When using strings, you must establish, and adhere to, a well defined contract for the format. For example, you should be cognizant of attributes like maximum length, legal characters, case-sensitivity, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. :white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable." -:white_check_mark: **DO** use [RFC3339] for date/time. +:white_check_mark: **DO** use [RFC3339] for date/time. :white_check_mark: **DO** use [RFC4122] for UUIDs. -##### Composite types -JSON also supports composing strings into higher order constructs, for example: -* **Object**: { "name" : value, … } -* **Array**: [ value, … ] - +##### Composite Types +JSON also supports composing strings into higher order constructs, for example: +* **Object**: { "name" : value, … } +* **Array**: [ value, … ] -:warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with updates when using ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. - -:ballot_box_with_check: **YOU SHOULD** use Maps instead of Arrays. +:warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with updates when using ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. +:ballot_box_with_check: **YOU SHOULD** use Maps instead of Arrays. #### Enums & SDKs (Client libraries) -It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI specification as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation. However, your services will have client libraries in many different programming languages. And because enumerations are handled differently depending on the language, this can lead to significant interoperability issues. +It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI specification as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation. However, your services will have client libraries in many different programming languages. And because enumerations are handled differently depending on the language, this can lead to significant interoperability issues. -To address these issues, Microsoft's tooling uses the concept of an "extensible enum," which effectively treats all enumerations as strings. In addition, "extensible enums" indicate to client libraries that the list of values is only a *partial* list. This enables the set of values to grow over time while ensuring stability in client libraries. +To address these issues, Microsoft's tooling uses the concept of an "extensible enum," which effectively treats all enumerations as strings. In addition, "extensible enums" indicate to client libraries that the list of values is only a *partial* list. This enables the set of values to grow over time while ensuring stability in client libraries. -:white_check_mark: **DO** use "extensible enums" +:white_check_mark: **DO** use "extensible enums" :ballot_box_with_check: **YOU SHOULD** be prepared to handle new values in your client @@ -428,11 +413,10 @@ While polymorphism is a powerful concept in programming languages, returing "pol The first issue is that developers writing code against this JSON string contract will have a very difficult time, especially in typed languages. It will be impossible to determine what the actual type is during development, minimizing the effectiveness of tooling. At runtime, developers will have to parse the JSON, interpret the "kind" value, and *then* cast to the proper sub-class. -Overall, this is a very brittle design that leads to a poor developer experience, especially over time. Consider the scenario when a new shape is introduced in V2 of the API. Existing client libraries that work with V1 will have no concept of this new shape and, when receiving an unknown shape, fail. +Overall, this is a very brittle design that leads to a poor developer experience, especially over time. Consider the scenario when a new shape is introduced in V2 of the API. Existing client libraries that work with V1 will have no concept of this new shape and, when receiving an unknown shape, fail. :warning: **YOU SHOULD NOT** use polymorphic types. Instead, return discriminate types. - ## Common API Patterns ### Performing an Action @@ -456,10 +440,7 @@ Overall, this is a very brittle design that leads to a poor developer experience ### Optimistic concurrency - ## Final Thoughts / Summary * Careful consideration up front * Long term decisions that are often codified in SDKs, CODE, etc. * Reach out and engage the stewardship team! - - From 9a2af307422113e6c7825aed112aeff0294a9db7 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Sat, 29 May 2021 10:09:17 -0700 Subject: [PATCH 096/729] Initial review --- azure/Guidelines.md | 304 +++++++++++++++++++++++--------------------- 1 file changed, 157 insertions(+), 147 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index be18e8c5..9f9194cf 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,5 +1,4 @@ - > ### Background > This document is a work in progress and is intended to become an updated version of the Azure REST API guidelines. It is based on the best practices for building REST APIs, the existing Microsoft and Azure API Guidelines, and feedback from the API Stewardship Board. Your thoughts, comments, issues, pull requests, and all other forms of feedback are welcomed and encouraged. You can also reach out to Mark Weitzel as well. > @@ -9,44 +8,39 @@ ---
-# Microsoft Azure REST API Guidelines - -## History - -| Date | Version | Notes | -| ----------- | ------- | --------------------------------------------------- | -| 2020-Mar-31 | v3.1 | 1st public release of the Azure REST API Guidelines | -| 2020-Jul-31 | v3.2 | Added service advice for initial versions | -| 2021-May-24 | WIP | This workstream opened to update and revise the guidelines| - - +# Microsoft Azure HTTP/REST API Guidelines ## Introduction -The Azure REST API guidelines are an extension of the [Microsoft REST API guidelines][1]. Readers of this document are assumed to be also reading the [Microsoft REST API guidelines][1] and be familiar with them. Azure guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Azure-specific guidance and additional details. While these guidelines represent and codify many years of experience building high performant, scalable cloud services on Azure, they are generally applicable to all REST APIs. Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. - -### Guideline Organization -The Guiding Principles section presents the high level concerns that affect all Azure services. Adherence to these principles creates consistent design, makes it easier for developers to use your service, and reduces the learning curve for other Azure services. All of these are critical to creating a delightful experience for Azure developers. +These guidelines offer prescriptive guidance that Azure service teams MUST follow ensuring that customers have a great experience by designing APIs meeting these goals: +- Developer friendly via consistent patterns & web standards (HTTP, REST, JSON) +- Efficient & cost-effective +- Work well with SDKs in many programming languages +- Customers can create fault-tolerant apps by supporting retries/idempotency +- Sustainable & versionable via clear API contracts with 2 requirements: + - Customer workloads must never break due to a service change + - Customers can adopt a version without requiring code changes -These guidelines are organized in three primary sections; Advice for new services, Building Blocks, and Common API patterns. Each section builds upon the other. For example, when updating resource collections, you should make sure to understand the difference in the HTTP verbs PUT and PATCH, as their behavior is quite different and directly affects how you expose the capability of your service. +Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. -#### Prescriptive Guidance -This document will be as prescriptive as possible. Specific guidance is labelled and color-coded to show the relative importance. In order from highest importance to lowest importance: +> NOTE: For an existing GA'd service, don't change/break its existing API; instead, leverage these concepts for future APIs while prioritizing consistency within your existing service. -:white_check_mark: **DO** adopt this guideline or follow this pattern. If you feel you need an exception, engage with the Architecture Board prior to implementation. +### Prescriptive Guidance +This document offers prescriptive guidance labeled as follows: -:no_entry: **DO NOT** follow this pattern. If you feel you need an exception, engage with the Architecture Board prior to implementation. +:white_check_mark: **DO** adopt this guideline or follow this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board[TODO: mail link? - not for public people] prior to implementation. -:ballot_box_with_check: **YOU SHOULD** strongly consider this guideline. If not following this advice, you MUST disclose the variance during the Architecture Board design review. +:no_entry: **DO NOT** follow this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. -:warning: **YOU SHOULD NOT** strongly consider avoiding the described pattern. If not following this advice, you MUST disclose the variance during the Architecture Board design review. +:ballot_box_with_check: **YOU SHOULD** strongly consider this guideline. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. -:heavy_check_mark: **YOU MAY** consider this guideline if appropriate to your situation. No notification to the architecture board is required. +:warning: **YOU SHOULD NOT** strongly consider avoiding the described pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. -*If you feel you need an exception, or need clarity based on your situation, please engage with the [API Stewardship Board] prior to release of your API.* +:heavy_check_mark: **YOU MAY** consider this guideline if appropriate to your situation. No notification to the Azure HTTP/REST Stewardship Board is required. +*If you feel you need an exception, or need clarity based on your situation, please contact the Azure HTTP/REST Stewardship Board prior to release of your API.* -## Advice for new services +## Advice for New Services Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, especially over time. This document provides Microsoft teams building Azure services with a set of guidelines that will help service teams build great APIs. The guidelines can be applied to create an API that is approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation, and enabling a broad audience of developers across multiple languages. @@ -61,8 +55,8 @@ Your goal is to create a developer friendly API where: ## Azure Management Plane vs Data Plane > Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. -> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on the conventions of the Azure ARM. +> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. ### Start with developer experience A great API starts with a well thought out and designed service. It is extremely difficult to create an elegant API that will work well on top of a service that is poorly designed. It is important that your development team builds some client code using the API. Hold reviews and share what is learend with your team. Engage with your customers during a preview release. If during a preview you discover that customers are struggling to use your API, e.g. they don't understand the abstraction layer, take the time to fix your service abstractions. This will benefit the developer and your team. Put yourself in the developer's shoes and think deeply about how they will be using your API and your service. @@ -110,8 +104,8 @@ A major inhibitor to adoption and usage is when an API behaves in an unexpected :ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. -### Design for Change Resiliancy -As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. +### Design for Resiliency +As you build out your service and API, there are a number of decisions that can be made up front that add resiliency. Addressing these as early as possible will help you iterate faster and avoid breaking changes. :ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. @@ -120,123 +114,103 @@ As you build out your service and API, there are a number of decisions that can :ballot_box_with_check: **YOU SHOULD** use wider data types (e.g. 64-bit vs. 32-bit) as they are more future-proof. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. ## Building Blocks: HTTP, REST, & JSON -The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet, namely HTTP, REST, and JSON. This section will provide you with a general understanding of how these technologies should be applied when creating your service. +The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet, namely HTTP, REST, and JSON. This section provides you with a general understanding of how to apply these technologies when design a service's API. ### HTTP -Azure services will adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231), as closely possible when presenting their API. This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: -* [Uniform Resource Locators (URLs)](URLS) -* HTTP Methods -* Headers -* Bodies +:ballot_box_with_check: **YOU SHOULD** adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231) -#### URLs +## URLs TODO: Update this section -A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. These are so central to the developer experience that careful consideration should be given when devising your URL structure. +A Uniform Resource Locator (URL) is how developers access your service's resources. The structure of the URL is critically important as it describes the service's cognitive model: -:ballot_box_with_check: **YOU SHOULD** keep URLs readable and if possible, avoid UUIDs & %-encoding (ex: Cádiz) +TODO: Show URL example -In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services. Specifically: - -:white_check_mark: **DO** expose their service to developers via the following URL pattern: +:white_check_mark: **DO** use the following URL pattern: ```text -https://.// +https://.///// ``` Where: -* **service** - the name of the service such as "blobstore", "servicebus", "directory", or "management" -* **cloud-instance** - the DNS domain name at the root of the cloud instance. For instance, public Azure uses `azure.net`. Sovereign clouds uses different domains. -* **service-defined-root** - the root of the service-specific path, such as "blobcontainer", "myqueue", etc. -* **unit-of-multi-tenancy** - refers to a globally unique moniker that identifies a unique container in the Azure service that has the following properties: - - * This container is the boundary of isolation between different tenants of the service. - * Quotas as set and enforced at the level of this container - but there will be different limits for different operations; and operations will be service specific. - * Resources in the service are attached to this container and are tied to this container in terms of lifecycle. For example someone signs up, they get this container. If they unsubscribe (or don’t pay their bills) then cleanup of this container occurs and the resources associated with this container are cleaned up. Cleanup follows a state machine – the container and the resources attached to it are deactivated first (and can be easily restored if required), and if no response for some period then deleted. - * It is the container for billing – which means the owner of this container sees one bill for the resource usage of all azure services under this container’s identifier. +* **service**: name of the service (ex: blobstore, servicebus, directory, or management) +* **cloud**: cloud domain name (see Azure CLI's "az cloud list") -For Azure PaaS services like SQL Azure, Azure Storage, Caching, etc., the unit of multi-tenancy is the Azure subscription id, which is a GUID. This ensures consistent access using the same URL pattern, and identifier, across all these services. + | Cloud | Domain | + | ------------- | ----- | + | Public | azure.net | + | China | chinacloudapi.cn | + | US Government | usgovcloudapi.net | + | German | cloudap.de | -##### Additional URL considerations -When services produce URLs in response headers or bodies, they **MUST** use a consistent form – either always a GUID for tenant identifier or always a single verified domain - regardless of the URL used to reach the resource. +* **tenant**: globally unique ID of container representing tenant isolation, billing, enforced quotas, lifetime of containees (ex: subscription UUID) -A service URL must be case-sensitive (except for scheme/host). If case doesn't match what you expect, the request __MUST__ fail with the appropriate HTTP return code. +* **service-root**: service-specific path (ex: blobcontainer, myqueue) -When returning information in a Response, services __MUST__ maintain and respect proper case values. +* **resrouce-collection**: holds items; use plural/lowercase noun; these names are almost always defined by the service; avoid >2 collections (TODO: Fix this last part - separate guideline bullet?) -Azure services __MUST NOT__ include Personal Identifying Information (PII) in the URL. +* **resource-id**: id or name of resource within the resource-collection; these names are frequently provided by clients -The Max length=2083 characters __MUST__ be observed. If a URL excedes this length, the service __MUST__ return a ```414-URI Too Long``` + :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz) -Legal characters for a URL ar: 0-9 A-Z a-z - . _ ~ / ? # [ ] @ ! $ & ' ( ) * + , ; = +:white_check_mark: **DO** use case-insensitive comparison for a URL's scheme (http or https) and host. -:ballot_box_with_check: **YOU SHOULD** reserve the following characters for use exclusive use: ```/ ? # [ ] @ ! $ & ' ( ) * + , ; =``` +:white_check_mark: **DO** use case-sensitive comparison for , , , , and . -### Direct endpoint URLs +:ballot_box_with_check: **YOU SHOULD** use case-sensitive comparison for -In addition to the required format above, services **MAY** also choose to expose direct endpoint for performance or routing reasons. The direct endpoint should be discoverable by clients, to ensure that developers are presented with a consistent pattern for accessing Azure services. +**YOU MAY** use case-insensitive comparison for a that is a GUID value -The format of the root of the direct endpoint **MUST** be as follows: - -```text -https://-..azure.net -``` +--- +A direct endpoint URL may also be used for performance/routing: -1. A request is made to the default endpoint (GET or HEAD). For example: + https://-../... - ```text - GET https://blobstore.azure.net/contoso.com/account1/container1/blob2 - ``` + Examples: + - Request URL: https://blobstore.azure.net/contoso.com/account1/container1/blob2 + - Response ```content-location``` [RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4): https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 + - GUID format: https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 +--- -2. That request is returned with the `Content-Location` header set to the direct endpoint. See [RFC2557]: +:white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a GUID for or always a single verified domain. - ```text - 200 OK - Content-Location: https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 - ``` +:white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. - Or, with the GUID format: +:no_entry: **DO NOT** include Personal Identifying Information (PII) in the URL. - ```text - 200 OK - Content-Location: https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 - ``` +:no_entry: **DO NOT** accept URLs with a length greater than 2083 characters; return ```414-URI Too Long``` -### HTTP Request / Response Pattern -The HTTP Request / Response pattern will dictate much of how your API behaves, for example; POST methods must be idempotent, GET methods may be cached, the If-Modified and etag headers determine your optimistic concurrency strategy. The URL of a service, along with its request / response, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you will make. For each request / response, the service: +:ballot_box_with_check: **YOU SHOULD** try to limit your URL's characters to ```0-9 A-Z a-z - . _ ~``` -:white_check_mark: **DO** validate all inputs to a request. +**YOU MAY** use these other characters but they will likely require %-encoding: ```/ ? # [ ] @ ! $ & ' ( ) * + , ; =``` -:white_check_mark: **DO** return the same object that was sent to the API in the response for all create or upsert operations. - -Because beacuse information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. +### HTTP Methods & Idempotency +Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, all service operations (including POST) must be idempotent. -:white_check_mark: **DO** use the following table when translating strings: - -Data type | Document string must be --------- | ------- -Boolean | true / false -Integer | -253+1 to +253-1 (limit due to IEEE-754 [RFC8259]())https://datatracker.ietf.org/doc/html/rfc8259) -Float | [IEEE-754 binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) -String | (Un)quoted?, max length, case-sensitive, multiple delimiter -UUID | {}? casing? hyphens? [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) -Date/Time (Header) | [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123) -Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) -Byte array | Base-64 encoded, max length +--- +> Exactly Once Behavior = Client Retries & Service Idempotency +--- -TODO: Expand the explanation for numbers. +:white_check_mark: **DO** ensure that ALL HTTP methods are idempotent. -#### Common Request & Response Headers -The table below lists the request / response headers most used by Azure services Service providers. +:warning: **YOU SHOULD NOT** use POST method unless you implement idempotently via an Idempotent-token header (TODO: fix this up). TODO: Say how POST returns the URL of the create resource with 201 -:white_check_mark: **DO** specify headers using kabob-style-text. +:white_check_mark: **DO** adhere to the return codes in the following table when implementing your API: -:white_check_mark: **DO** use all lowercase for headers. +Method | Description | Response Status Code +----|----|---- +GET | Read the resource | 200-OK +DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found +PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created +PUT | Create/Replace the *whole* resource | 200-OK, 201-Created + +TODO: To get a collection's resources (GET; see the collection section) -:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. In addition, each request / response header. +**YOU MAY** support caching and optimistic concurrency by returning resources with an etag response header and by supporting the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers. -:no_entry: **DO NOT** use "x-" prefix for headers, unless the header already exists in production. +### HTTP Query Parameters and Header Values +The table below lists the headers most used by Azure services: Header Key | Applies to | Example ------------ | ------------- | ------------- @@ -247,9 +221,9 @@ tracecontext | Request | [see Distributed Tracing](http://TODO:link-goes-here) accept | Request | application/json if-match | Request | "67ab43" or * (no quotes) (see Conditional Access) if-none-match | Request | "67ab43" or * (no quotes) [see Conditional Access](http://TODO:link-goes-here) -If-Modified-Since | | Request | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) +If-Modified-Since | Request | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) If-Unmodified-Since | Request | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) -date [RFC1123] | Both | Sun, 06 Nov 1994 08:49:37 GMT +date [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123) | Both | Sun, 06 Nov 1994 08:49:37 GMT *content-type* | Both | application/merge-patch+json *content-length* | Both | 1024 *x-ms-request-id* | Response | [see Customer Support](http://TODO:link-goes-here) @@ -258,32 +232,49 @@ retry-after | Response | 180 [see Throttling Client Requests] *x-ms-error-code* | Response | [see Processing a REST Request](http://TODO:link-goes-here) Last-Modified | Response | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) -TODO: Fix the links. +:white_check_mark: **DO** specify headers using kabob-casing. +:white_check_mark: **DO** compare request header names using case-insensitivity -#### HTTP Methods & Idempotency -Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. +:white_check_mark: **DO** compare request header values using case-sensitivity. Some exceptions exist: user-agent?, accept?, content-type?, RFC1123 dates, guids?. -:white_check_mark: **DO** implement all operations idempotently, ideally from the outset. +:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. In addition, each request / response header. -:warning: **YOU SHOULD NOT** using the POST method unless you can guarantee it can be implemented idempotently. +:no_entry: **DO NOT** use "x-" prefix for headers, unless the header already exists in production. -:white_check_mark: **DO** adhere to the return codes in the following table when implementing your API: +:white_check_mark: **DO** validate all query parameter and request header values. TODO: What to return on failure -Method | Description | Response Status Code -----|----|---- -GET | Read the resource | 200-OK -DELETE | Remove the resource | 204-No Content; avoid 404-Not Found -PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created -PUT | Create/Replace the *whole* resource | 200-OK, 201-Created +:white_check_mark: **DO** return the state of the resource after a PUT, PATCH, or GET operation with a ```200-OK``` or ```201-Created```. + +:white_check_mark: **DO** return a ```204-No Content``` without a resource for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) + +Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. + +:white_check_mark: **DO** use the following table when translating strings: + +Data type | Document that string must be +-------- | ------- +Boolean | true / false (all lowercase) +Integer | -253+1 to +253-1 (limit due to IEEE-754 [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259)) +Float | [IEEE-754 binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) +String | (Un)quoted?, max length, legal characters, case-sensitive, multiple delimiter +UUID | {}? casing? hyphens? [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) +Date/Time (Header) | [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123) +Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) +Byte array | Base-64 encoded, max length + +TODO: Expand the explanation for numbers. + +TODO: Fix the links. #### Additional References * [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) * [Standard HTTP Headers](https://httpwg.org/specs/rfc7231.html#header.field.registration) * [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) -### REST -REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you will define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to retrieve and update the state of your resource. Note, it's important to model resource state, not to behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. +### REST (REpresentational State Transfer) +REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment,reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's URLs determine the hierarchical path developers use to create, update & retrieve the state of a resource. Note: it's important to model resource state, not behavior. There are patterns, later in these guidelines, that describe how to invoke an action on a resource. + TODO: Add link to behavior section When designing your service, it is important to optimize for the developer using your API. @@ -299,38 +290,57 @@ When designing your service, it is important to optimize for the developer using :white_check_mark: **DO** use proper response codes/payloads so customer can self-fix #### JSON Resource Schema & Field Mutability -For a given URL path, the JSON schema (data type) should be the same for PATCH, PUT, GET, DELETE, and GETting collection items. This allows one SDK type for input/output operations and enables the response to be passed back in request. While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. +:white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH request/response, GET response, and POST response. This allows one SDK type for input/output operations and enables the response to be passed back in request. + +--- +Jeff: What to say about required fields - it not really a REST thing +TODO: Some fields may be marked as required indicating that their value must alway be sent and is always returned. +> NOTE: A service is not allowed to introduce new required fields or remove any required fields in newer versions of the service. +For PATCH, the their must be a similar JSON schema with no required fields nullable. +--- + +--- +This i also not really a rest thing; more of a service implementation thing +While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. REMOVE?: Tools like ADL do allow annotation of mutability, enabling more sophisticated code generation of client libraries. :white_check_mark: **DO** create a model of your data types. For each field, apply one of the following rules: Field Mutability | Service Request's behavior for this field ----| ---- -**Create** | Service honors field only when creating a resource *Note: Minimize create-only fields so customers don't have to delete & re-create the resource.* +**Create** | Service honors field only when creating a resource. Minimize create-only fields so customers don't have to delete & re-create the resource. **Update** | Service honors field when creating or updating a resource **Read** |Service fails request (or 'Accept' if it matches what's in the resource); returns these symmetric fields in a response +TODO: Fit 'required' into this story (Jeff) +--- + #### General guidelines The following are general guidelines when using REST: :white_check_mark: **DO** serve GET for resource retrieval and send JSON in the response body. -:white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. +:white_check_mark: **DO** create and update resource using PATCH [RFC5789] with JSON Merge Patch [RFC7396](https://datatracker.ietf.org/doc/html/rfc7396) request body. TODO: The response should be the JSON schema WITH required fields (not the same schema as passed in) + +:white_check_mark: **DO** use PUT with JSON for wholesale create/update operations. +> NOTE: If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent tof DELETE followed by PUT). + +:white_check_mark: **DO** use DELETE to remove a resource. -:white_check_mark: **DO** use PUT with JSON for wholesale create/update update operations. Take special care to hand versioning issues properly. +TODO: If we keep this NOTE, then it is about IDs, not about DELETE: NOTE: Ids are "Customer Content" & Azure allows their use. -:white_check_mark: **DO** use DELETE when removing resources. NOTE: Ids are "Customer Content" & Azure allows their use. +:white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. -:white_check_mark: **DO** make the payloads for PUT, PATCH, GET the same. +:white_check_mark: **DO** treat JSON field names with case-sensitivity. -:white_check_mark: **DO** make fields simple. +:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions but avoid if at all possible. -:white_check_mark: **DO** preserve string casing/array order. +:no_entry: **DO** fail an operation with ```400-Bad Request``` if the request JSON body is improperly-formed JSON. -:no_entry: **DO NOT** let an operation succeeed if unknown fields or bad values are passed. +:no_entry: **DO** fail an operation with ```412-Unprocessable Entity``` if any JSON field name or value is not fully understood by the specific version of the service. -:no_entry: **DO NOT** return secret fields via GET. For exampple, do not return adminPassword in JSON. +:no_entry: **DO NOT** return secret fields via GET. For example, do not return adminPassword in JSON. -:heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. +:heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. #### Process a PATCH/PUT request PATCH/PUT requests accept a subset of fields. Because of this, they require additional guidelines handling requests and responses. In general, you want to avoid creating partial resources as a result of create operations. @@ -352,15 +362,15 @@ PUT | Overwrite resource entirely using Create/Update fields | 200-OK #### Handling Errors -:white_check_mark: **DO** deturn x-ms-error-code header with string +:white_check_mark: **DO** return x-ms-error-code header with string -:white_check_mark: **DO** ensure your service returns the error response body +:white_check_mark: **DO** ensure your service returns the error response body (TODO: show it here) ### JSON Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: -* **Boolean:** true/false -* **Number:** signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) -* **String:** used for everything else +* **Boolean:** true/false +* **Number:** signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) +* **String:** used for everything else :white_check_mark: **DO** use integers within the acceptable range of JSON number. @@ -371,14 +381,14 @@ When using strings, you must establish, and adhere to, a well defined contract f :white_check_mark: **DO** use [RFC3339] for date/time. :white_check_mark: **DO** use [RFC4122] for UUIDs. -##### Composite Types -JSON also supports composing strings into higher order constructs, for example: -* **Object**: { "name" : value, … } -* **Array**: [ value, … ] +##### Composite types +JSON also supports composing strings into higher order constructs, for example: +* **Object**: { "name" : value, … } +* **Array**: [ value, … ] -:warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with updates when using ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. +:warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with updates when using ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. -:ballot_box_with_check: **YOU SHOULD** use Maps instead of Arrays. +:ballot_box_with_check: **YOU SHOULD** use maps instead of arrays. #### Enums & SDKs (Client libraries) @@ -388,11 +398,11 @@ To address these issues, Microsoft's tooling uses the concept of an "extensible :white_check_mark: **DO** use "extensible enums" -:ballot_box_with_check: **YOU SHOULD** be prepared to handle new values in your client +:ballot_box_with_check: **DO** document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow. :no_entry: **DO NOT** send "enum integers" over the wire. -:no_entry: **DO NOT** remove items from your enumerated list. This will likely result in a breaking change to client libraries. +:no_entry: **DO NOT** remove values from your enumeration list. This will likely result in a breaking change to client libraries & customers. #### Discriminate polymorphic types While polymorphism is a powerful concept in programming languages, returing "polymorphic JSON" as part of an API introduces significant complexity for implementors of client libraries and developers, especially as new versions of your service are introduced. For example, consider a service where V1 introduces two shapes, Retangles and Circles. They could be represented in JSON as follows: @@ -443,4 +453,4 @@ Overall, this is a very brittle design that leads to a poor developer experience ## Final Thoughts / Summary * Careful consideration up front * Long term decisions that are often codified in SDKs, CODE, etc. -* Reach out and engage the stewardship team! +* Reach out and engage the stewardship team! \ No newline at end of file From ab2102f24599c59ad27275db6521b50e6927ca28 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Sat, 29 May 2021 10:30:33 -0700 Subject: [PATCH 097/729] Chnage order of guidance labels from DO to DO NOT --- azure/Guidelines.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 9f9194cf..082b1ede 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -30,13 +30,13 @@ This document offers prescriptive guidance labeled as follows: :white_check_mark: **DO** adopt this guideline or follow this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board[TODO: mail link? - not for public people] prior to implementation. -:no_entry: **DO NOT** follow this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. - :ballot_box_with_check: **YOU SHOULD** strongly consider this guideline. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. +:heavy_check_mark: **YOU MAY** consider this guideline if appropriate to your situation. No notification to the Azure HTTP/REST Stewardship Board is required. + :warning: **YOU SHOULD NOT** strongly consider avoiding the described pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. -:heavy_check_mark: **YOU MAY** consider this guideline if appropriate to your situation. No notification to the Azure HTTP/REST Stewardship Board is required. +:no_entry: **DO NOT** follow this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. *If you feel you need an exception, or need clarity based on your situation, please contact the Azure HTTP/REST Stewardship Board prior to release of your API.* From 93e6ddf3217e9437483c3164c665277444f5e12a Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Wed, 2 Jun 2021 16:56:09 -0400 Subject: [PATCH 098/729] cleaned up URL section --- azure/Guidelines.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 082b1ede..a52bf177 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -120,12 +120,8 @@ The Microsoft Azure Cloud platform exposes its APIs through the core building bl :ballot_box_with_check: **YOU SHOULD** adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231) ## URLs -TODO: Update this section - A Uniform Resource Locator (URL) is how developers access your service's resources. The structure of the URL is critically important as it describes the service's cognitive model: -TODO: Show URL example - :white_check_mark: **DO** use the following URL pattern: ```text @@ -152,7 +148,12 @@ Where: * **resource-id**: id or name of resource within the resource-collection; these names are frequently provided by clients - :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz) +A well formed Azure service URL would look similar to the following: + +TODO: Show URL example + + +:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz) :white_check_mark: **DO** use case-insensitive comparison for a URL's scheme (http or https) and host. @@ -160,7 +161,7 @@ Where: :ballot_box_with_check: **YOU SHOULD** use case-sensitive comparison for -**YOU MAY** use case-insensitive comparison for a that is a GUID value +:heavy_check_mark: **YOU MAY** use case-insensitive comparison for a that is a GUID value --- A direct endpoint URL may also be used for performance/routing: @@ -183,7 +184,7 @@ A direct endpoint URL may also be used for performance/routing: :ballot_box_with_check: **YOU SHOULD** try to limit your URL's characters to ```0-9 A-Z a-z - . _ ~``` -**YOU MAY** use these other characters but they will likely require %-encoding: ```/ ? # [ ] @ ! $ & ' ( ) * + , ; =``` +:heavy_check_mark: **YOU MAY** use these other characters but they will likely require %-encoding: ```/ ? # [ ] @ ! $ & ' ( ) * + , ; =``` ### HTTP Methods & Idempotency Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, all service operations (including POST) must be idempotent. From 9e28d7329075ffeea90279549e90e4b1b4643319 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Wed, 2 Jun 2021 19:20:46 -0400 Subject: [PATCH 099/729] Initial pass at versioning section --- azure/Guidelines.md | 240 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 234 insertions(+), 6 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a52bf177..48d31f6e 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -206,7 +206,7 @@ DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created PUT | Create/Replace the *whole* resource | 200-OK, 201-Created -TODO: To get a collection's resources (GET; see the collection section) +TODO: To get a collection's resources (GET; see the collection section) **YOU MAY** support caching and optimistic concurrency by returning resources with an etag response header and by supporting the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers. @@ -294,8 +294,10 @@ When designing your service, it is important to optimize for the developer using :white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH request/response, GET response, and POST response. This allows one SDK type for input/output operations and enables the response to be passed back in request. --- -Jeff: What to say about required fields - it not really a REST thing -TODO: Some fields may be marked as required indicating that their value must alway be sent and is always returned. +Jeff: What to say about required fields - it not really a REST thing +TODO: Some fields may be marked as required indicating that their value must alway be sent and is always returned. + + > NOTE: A service is not allowed to introduce new required fields or remove any required fields in newer versions of the service. For PATCH, the their must be a similar JSON schema with no required fields nullable. --- @@ -310,7 +312,7 @@ Field Mutability | Service Request's behavior for this field ----| ---- **Create** | Service honors field only when creating a resource. Minimize create-only fields so customers don't have to delete & re-create the resource. **Update** | Service honors field when creating or updating a resource -**Read** |Service fails request (or 'Accept' if it matches what's in the resource); returns these symmetric fields in a response +**Read** | Service fails request (or accept if they match what's in the resource); returns these fields in a response TODO: Fit 'required' into this story (Jeff) --- @@ -340,9 +342,12 @@ TODO: If we keep this NOTE, then it is about IDs, not about DELETE: NOTE: Ids ar :no_entry: **DO** fail an operation with ```412-Unprocessable Entity``` if any JSON field name or value is not fully understood by the specific version of the service. :no_entry: **DO NOT** return secret fields via GET. For example, do not return adminPassword in JSON. - + +:no_entry: **DO NOT** add computed output values if the computed value can be calculated from other information in the payload. It unnecessarily expands the payload. + :heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. + #### Process a PATCH/PUT request PATCH/PUT requests accept a subset of fields. Because of this, they require additional guidelines handling requests and responses. In general, you want to avoid creating partial resources as a result of create operations. @@ -436,10 +441,233 @@ Overall, this is a very brittle design that leads to a poor developer experience ### Collections -### Long Running Operations ### API Versioning +Versioning is a powerful technique that helps you provide consistent and stable APIs to developers that use your service. The Microsoft REST API guidelines offer different options on how to specify an API version and guidance on what constitutes a breaking change. However, this section refines that guidance in order to drive greater consistency across the Azure services and stack, as well as the developer tooling, and client libraries. + +:white_check_mark: **DO** use explicit versioning for your service API. + +:white_check_mark: **DO** follow the standard [_Azure Global Retirements and Breaking Changes_][7] when retiring your API. + +#### Specifying the version in Azure +The Microsoft REST API guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. For consistency across the Azure portfolio, you must specify the version using a query parameter. + +Examples: +```text +GET https://blobstore.azure.com/foo.com/acct1/c1/blob2?api-version=1.0 +PUT https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 +POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 +``` + +#### API Changes that require a version change + +There are three groups of changes that may happen to an API. + +1. Changes made to an **EXISTING** API version due to security or compliance reasons. We shall refer to these types of changes as _Compliance changes_. +2. Changes made to an API that may cause a client making the API call to fail, such as removal of an endpoint or property or changing the format of the body. We refer to these types of changes as _Breaking changes_. +3. Additive changes made to an API that do not cause a client making the API call to fail, such as the addition of a new optional property or a new endpoint. We refer to these types of changes as _Evolutionary changes_. + +With the exception of _Compliance changes_ (which are extremely rare), Azure services must update the version number of their API whenever there is a change to the API, _no matter how small_. Customers will "lock the API version" so that their code does not fail when the service introduces new features and will rely on the fact that your API version is a contract with the services that will never change. + +A _breaking change_ is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Removing an endpoint from an API is always a _breaking change_. Adding a new endpoint is always an _evolutionary change_. Changes to properties may be _evolutionary_ or _breaking_ depending on the type of change and whether the change is to an input parameter or output parameter: + +| Property change | Input | Output | +|:-----------------------|:------------:|:------------:| +| Remove a property | Breaking | Breaking | +| Add optional property | Evolutionary | Breaking | +| Add required property | Breaking | Breaking | +| Data type change | Breaking | Breaking | +| Format change | Breaking | Breaking | +| Integer widens | Evolutionary | Breaking | +| Integer narrows | Breaking | Evolutionary | +| Add new value to enum | Evolutionary | Breaking | +| Remove value from enum | Breaking | Breaking | +| Optional to required | Breaking | Breaking | +| Required to optional | Evolutionary | Breaking | + + +:ballot_box_with_check: **YOU SHOULD** use a date oriented semantic as the version numbering scheme of your API, e.g. `2021-05-01-preview.1`. + +:white_check_mark: **DO** increment the version of your API for _Compliance changes_. + +:white_check_mark: **DO** review any breaking changes with the Azure REST API stewardship board prior to seecing approval through the [Azure Global Breaking Change Policy][7]. + +:white_check_mark: **DO** follow the [Azure Global Retirement Policy][7] for any deprecated APIs. + +:white_check_mark: **DO** increment the major version (if using SemVer) for any breaking changes. + +:white_check_mark: **DO** increment the version bump for _evolutionary changes. If the service is using SemVer for versioning, evolutionary changes constitute a minor version change. + +:ballot_box_with_check: **YOU SHOULD** review any _evolutionary change_ with the Azure API stewardship board. + + +#### Changing the API without changing the version + +Because the API version represents a contract that a developer can rely on when generating SDKs to communicate with the service, there are a limited set of situations where changing the API is permissable without a version bump. The only changes universally allowed: + +1. Adding a new (optional) value to an extensible enum. + +An extensible enum is (in essence) a string. The values of the extensible enum drive intellisense and documentation, but the values are not considered exhaustive. + +If a service is **ONLY** available in the Azure public cloud, then an additional situation can be used to add functionality without changing the version: + +1. Adding a new (optional) query parameter to adjust the output (for example, adding filter options to a list operation). +2. Adding optional computed read-only output values that are generated based on the new (optional) query parameters. + +For example, let's say an image service wants to add bounding-box information to the output of an operation. The service can add a new query parameter `includeBoundingBox=true` and then include the bounding box information within the output only when the new query parameter is specified. A version bump is recommended, but not required. + +If not changing the API version, the service **MUST** update all data centers before the new query parameter is advertised to customers. + +:no_entry: **DO NOT** use this mechanism to get around the version bump. Adding such query parameters results in sub-optimal API designs and should only be used for exceptional circumstances. + +This functionality is only available for single cloud deployments because the API version specifies the contract with the developer. Consider, for example, if such a functionality was included in Azure public cloud and not a sovereign cloud. A developer creating an SDK based on this functionality may see the application work in one cloud but fail when targeting the other despite using the same API version in both cases. For the purposes of this situation, "other clouds" includes Azure Stack and other deployment mechanisms such as containers for on-premise usage. + + +All situations where the API definition is changed (irrespective of whether a version change happens or not) + **MUST** be reviewed by the Azure REST API Review Board before release. + +#### Preview Versions + +Preview versions of the API can be indicated by adding the suffix `-preview.X` to the end of the API version, where `X` is an incrementing integer. For example: + +* `2021-05-01-preview.1` +* `1.0-preview.2` + +Preview versions are not treated the same way as release versions. In general, there are two types of previews: + +* **Private** previews are released to a known subset of users. The service team knows how to contact each person within the private preview. There are no restrictions on changes within a private preview, as long as the service team communicates effectively with their users on what changes are made and when they will be made. + +* **Public** previews are released broadly, but contain APIs that may change between previews and may be deleted prior to the final version. The `X` integer (indicating the revision of the preview) must be incremented for all breaking changes (resulting in a new API version). Evolutionary changes may be added as needed, as long as the change is communicated broadly. + +You should follow the axiom "don't surprise your customers" when deciding whether to increment the preview version, and err on the side of incrementing the preview version. + +Preview APIs [follow a different path for deprecation and retirement](https://dev.azure.com/msazure/AzureWiki/_wiki/wikis/AzureWiki.wiki/37683/Retirement-of-Previews), and are not subject to the same guarantees as APIs marked as generally available. Preview APIs have a life-span of not more than 12 months, after which they must be retired. + +#### Why Azure recommends conservative API versioning + +Azure history is replete with anecdotes that directly relate to API versioning. For instance, Cognitive Services unintentionally broke customers by making changes to the API structure without a version bump with updates that they did not think would be breaking changes. These changes led customers to question the stability and maturity of the product and increased the churn rate for the services. + +Even changes that are evolutionary can cause problems. For instance, let's say that a service adds a new feature via a new endpoint in the API. The SDK gets updates to support this new API, but the service does not bump the version number. Since the roll out of the new feature is not atomic, there is a period of time (potentially months long) where the feature is available in some regions but not others. A customer has the potential for attempting to use the feature in two different regions and having it work in one region but not the other, despite the two regions supporting the same version number. This is only made worse when we consider Azure Stack, which can be upwards of a year behind the public cloud offerings. + +There are a few mechanisms that can reduce breaking changes and their effects on our customers. + +#### Use PATCH instead of PUT for updates + +The HTTP PUT verb is an idemopotent apply operation for the API version being used. The [Microsoft API Guidelines already recommend the use of PATCH](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#742-patch) for updates. + +Consider the following sequence: + +* User1 creates a resource with version v2, using a new optional parameter. +* Later, User2 wants to update the resource using unrelated settings. Using version v1, User2 issues a GET, does the changes, and then issues a PUT to replace the resource definition. + +In this case, the optional parameter is lost because of the replace semantics. The optional parameter only exists on API version v2, and not on version v1. + +Service teams SHOULD prefer and recommend PATCH operations for updating resources. + +#### Use extensible enums + +While removing a value from an enum is a breaking change, adding an enum can be handled with an _extensible enum_. An extensible enum is a string value that has been marked with a special marker - setting `modelAsString` to true within an `x-ms-enum` block. For example: + +```json +"createdByType": { + "type": "string", + "description": "The type of identity that created the resource.", + "enum": [ + "User", + "Application", + "ManagedIdentity", + "Key" + ], + "x-ms-enum": { + "name": "createdByType", + "modelAsString": true + } +} +``` + +Always model an enum as a string unleess you are positive that the symbol set will **NEVER** change over time. + +#### Group versioning in Azure and Azure Stack + +Azure Stack allows customers and hosters to deploy their own small versions of Azure and upgrade it at a different pace than Azure. In order to make it possible to write an application or SDK that targets both Azure and Azure Stack, additional versioning policy is necessary. Contact the Azure Stack team for further guidance. + +#### Version discovery + +Simpler clients may be hardcoded to a single version of a service. Since Azure services offer each version for a well-known period of time, a client that’s regularly maintained can be always operational without further complexity as long as during regular maintenance the client is moved forward to new versions in advance of older ones being retired. + +API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. + +:white_check_mark: **DO** support API version discovery, including + +1. Support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service +2. Include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. +3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it must include those versions (group and major.minor) in the `api-deprecated-versions` header. +4. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. + +:ballot_box_with_check: **YOU SHOULD** support the following for version discovery: + +1. In addition to the functionality described here, services should support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. +2. Services should allow unauthenticated HTTP `OPTIONS` requests. When doing so, authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. +3. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request should return the group versions that are supported across the expanded set. + +Example request to discover API versions (blob storage container list API): + +```text +OPTIONS /?comp=list HTTP/1.1 +host: accountname.blob.core.azure.net +``` + +Example response: + +```text +200 OK +api-supported-versions: 2011-08,2012-02,1.1,2.0 +api-deprecated-versions: 2009-04,1.0 +Content-Length: 0 +``` + +Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. + +#### Retiring pre-release and beta APIs + +Pre-release and beta APIs are not covered by the Azure Global Retirement and Deprecation Policy. Each team providing a preview API should communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. Despite their unstable nature and the fact they are not officially supported, **customers have suffered downtime when we have deprecated preview APIs**. + +:ballot_box_with_check: **YOU SHOULD** should monitor preview endpoints closely. + +:ballot_box_with_check: **YOU SHOULD** consider following the normal deprecation policy. + +##### Additional References + + +[1]: https://github.com/microsoft/api-guidelines +[RFC2557]: https://www.ietf.org/rfc/rfc2557.txt + + +[2]: https://aka.ms/armwiki +[3]: https://github.com/Azure/azure-resource-manager-rpc + + +[OpenAPI Specification]: https://github.com/Azure/adx-documentation-pr/wiki/Getting-started-with-OpenAPI-specifications + + +[6]: https://support.microsoft.com/en-us/help/30881 +[7]: http://aka.ms/aprwiki + + +### Long Running Operations + +The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. + +:white_check_mark: **DO** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. + +:white_check_mark: **DO** return the same value for **both** headers. + +:white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. + + + + ### Distributed Tracing & Service Telemetry ### Jobs From 64a85067b61b067be5c9024a518ec0bf614e70df Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 4 Jun 2021 15:41:58 +0000 Subject: [PATCH 100/729] fix merge conflicts --- azure/Guidelines.md | 67 +++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 48d31f6e..7758ded6 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -55,8 +55,7 @@ Your goal is to create a developer friendly API where: ## Azure Management Plane vs Data Plane > Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. - -> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. +> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on the conventions of the Azure ARM. ### Start with developer experience A great API starts with a well thought out and designed service. It is extremely difficult to create an elegant API that will work well on top of a service that is poorly designed. It is important that your development team builds some client code using the API. Hold reviews and share what is learend with your team. Engage with your customers during a preview release. If during a preview you discover that customers are struggling to use your API, e.g. they don't understand the abstraction layer, take the time to fix your service abstractions. This will benefit the developer and your team. Put yourself in the developer's shoes and think deeply about how they will be using your API and your service. @@ -114,15 +113,26 @@ As you build out your service and API, there are a number of decisions that can :ballot_box_with_check: **YOU SHOULD** use wider data types (e.g. 64-bit vs. 32-bit) as they are more future-proof. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. ## Building Blocks: HTTP, REST, & JSON -The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet, namely HTTP, REST, and JSON. This section provides you with a general understanding of how to apply these technologies when design a service's API. +The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet, namely HTTP, REST, and JSON. This section will provide you with a general understanding of how these technologies should be applied when creating your service. ### HTTP -:ballot_box_with_check: **YOU SHOULD** adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231) +Azure services will adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231), as closely possible when presenting their API. This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: +* [Uniform Resource Locators (URLs)](URLS) +* HTTP Methods +* Headers +* Bodies + +#### URLs +TODO: Update this section + +A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. These are so central to the developer experience that careful consideration should be given when devising your URL structure. ## URLs A Uniform Resource Locator (URL) is how developers access your service's resources. The structure of the URL is critically important as it describes the service's cognitive model: -:white_check_mark: **DO** use the following URL pattern: +In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services. Specifically: + +:white_check_mark: **DO** expose their service to developers via the following URL pattern: ```text https://.///// @@ -144,9 +154,9 @@ Where: * **service-root**: service-specific path (ex: blobcontainer, myqueue) -* **resrouce-collection**: holds items; use plural/lowercase noun; these names are almost always defined by the service; avoid >2 collections (TODO: Fix this last part - separate guideline bullet?) +A service URL must be case-sensitive (except for scheme/host). If case doesn't match what you expect, the request __MUST__ fail with the appropriate HTTP return code. -* **resource-id**: id or name of resource within the resource-collection; these names are frequently provided by clients +When returning information in a Response, services __MUST__ maintain and respect proper case values. A well formed Azure service URL would look similar to the following: @@ -155,9 +165,7 @@ A well formed Azure service URL would look similar to the following: :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz) -:white_check_mark: **DO** use case-insensitive comparison for a URL's scheme (http or https) and host. - -:white_check_mark: **DO** use case-sensitive comparison for , , , , and . +### Direct endpoint URLs :ballot_box_with_check: **YOU SHOULD** use case-sensitive comparison for @@ -178,9 +186,8 @@ A direct endpoint URL may also be used for performance/routing: :white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. -:no_entry: **DO NOT** include Personal Identifying Information (PII) in the URL. - -:no_entry: **DO NOT** accept URLs with a length greater than 2083 characters; return ```414-URI Too Long``` +### HTTP Request / Response Pattern +The HTTP Request / Response pattern will dictate much of how your API behaves, for example; POST methods must be idempotent, GET methods may be cached, the If-Modified and etag headers determine your optimistic concurrency strategy. The URL of a service, along with its request / response, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you will make. For each request / response, the service: :ballot_box_with_check: **YOU SHOULD** try to limit your URL's characters to ```0-9 A-Z a-z - . _ ~``` @@ -239,14 +246,23 @@ Last-Modified | Response | (RFC1123) [see Optimistic Concurrency](http://TODO:li :white_check_mark: **DO** compare request header values using case-sensitivity. Some exceptions exist: user-agent?, accept?, content-type?, RFC1123 dates, guids?. +#### HTTP Methods & Idempotency +Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. :ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. In addition, each request / response header. :no_entry: **DO NOT** use "x-" prefix for headers, unless the header already exists in production. +:warning: **YOU SHOULD NOT** using the POST method unless you can guarantee it can be implemented idempotently. :white_check_mark: **DO** validate all query parameter and request header values. TODO: What to return on failure :white_check_mark: **DO** return the state of the resource after a PUT, PATCH, or GET operation with a ```200-OK``` or ```201-Created```. +Method | Description | Response Status Code +----|----|---- +GET | Read the resource | 200-OK +DELETE | Remove the resource | 204-No Content; avoid 404-Not Found +PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created +PUT | Create/Replace the *whole* resource | 200-OK, 201-Created :white_check_mark: **DO** return a ```204-No Content``` without a resource for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. @@ -273,9 +289,8 @@ Byte array | Base-64 encoded, max length * [Standard HTTP Headers](https://httpwg.org/specs/rfc7231.html#header.field.registration) * [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) -### REST (REpresentational State Transfer) -REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment,reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's URLs determine the hierarchical path developers use to create, update & retrieve the state of a resource. Note: it's important to model resource state, not behavior. There are patterns, later in these guidelines, that describe how to invoke an action on a resource. - +### REST +REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you will define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to retrieve and update the state of your resource. Note, it's important to model resource state, not to behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. TODO: Add link to behavior section When designing your service, it is important to optimize for the developer using your API. @@ -291,6 +306,7 @@ When designing your service, it is important to optimize for the developer using :white_check_mark: **DO** use proper response codes/payloads so customer can self-fix #### JSON Resource Schema & Field Mutability +For a given URL path, the JSON schema (data type) should be the same for PATCH, PUT, GET, DELETE, and GETting collection items. This allows one SDK type for input/output operations and enables the response to be passed back in request. While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. :white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH request/response, GET response, and POST response. This allows one SDK type for input/output operations and enables the response to be passed back in request. --- @@ -303,7 +319,7 @@ For PATCH, the their must be a similar JSON schema with no required fields nulla --- --- -This i also not really a rest thing; more of a service implementation thing +This is also not really a rest thing; more of a service implementation thing While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. REMOVE?: Tools like ADL do allow annotation of mutability, enabling more sophisticated code generation of client libraries. :white_check_mark: **DO** create a model of your data types. For each field, apply one of the following rules: @@ -314,7 +330,6 @@ Field Mutability | Service Request's behavior for this field **Update** | Service honors field when creating or updating a resource **Read** | Service fails request (or accept if they match what's in the resource); returns these fields in a response -TODO: Fit 'required' into this story (Jeff) --- #### General guidelines @@ -322,7 +337,7 @@ The following are general guidelines when using REST: :white_check_mark: **DO** serve GET for resource retrieval and send JSON in the response body. -:white_check_mark: **DO** create and update resource using PATCH [RFC5789] with JSON Merge Patch [RFC7396](https://datatracker.ietf.org/doc/html/rfc7396) request body. TODO: The response should be the JSON schema WITH required fields (not the same schema as passed in) +:white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. :white_check_mark: **DO** use PUT with JSON for wholesale create/update operations. > NOTE: If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent tof DELETE followed by PUT). @@ -375,8 +390,8 @@ PUT | Overwrite resource entirely using Create/Update fields | 200-OK ### JSON Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: * **Boolean:** true/false -* **Number:** signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) -* **String:** used for everything else +* **Number:** signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) +* **String:** used for everything else :white_check_mark: **DO** use integers within the acceptable range of JSON number. @@ -388,13 +403,13 @@ When using strings, you must establish, and adhere to, a well defined contract f :white_check_mark: **DO** use [RFC4122] for UUIDs. ##### Composite types -JSON also supports composing strings into higher order constructs, for example: +JSON also supports composing strings into higher order constructs, for example: * **Object**: { "name" : value, … } -* **Array**: [ value, … ] +* **Array**: [ value, … ] -:warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with updates when using ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. +:warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with updates when using ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. -:ballot_box_with_check: **YOU SHOULD** use maps instead of arrays. +:ballot_box_with_check: **YOU SHOULD** use maps instead of arrays. #### Enums & SDKs (Client libraries) @@ -682,4 +697,4 @@ The Microsoft REST API guidelines for Long Running Operations are an updated, cl ## Final Thoughts / Summary * Careful consideration up front * Long term decisions that are often codified in SDKs, CODE, etc. -* Reach out and engage the stewardship team! \ No newline at end of file +* Reach out and engage the stewardship team! From 5b407ecdfcf9d86a16dbfa4d917101a12b2d1fd7 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 4 Jun 2021 16:50:37 -0400 Subject: [PATCH 101/729] Minor reorg of WIP sections at the end. --- azure/Guidelines.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 7758ded6..497c45a2 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -452,11 +452,8 @@ Overall, this is a very brittle design that leads to a poor developer experience ### Performing an Action -### Conditional Access - ### Collections - ### API Versioning Versioning is a powerful technique that helps you provide consistent and stable APIs to developers that use your service. The Microsoft REST API guidelines offer different options on how to specify an API version and guidance on what constitutes a breaking change. However, this section refines that guidance in order to drive greater consistency across the Azure services and stack, as well as the developer tooling, and client libraries. @@ -670,7 +667,7 @@ Pre-release and beta APIs are not covered by the Azure Global Retirement and Dep [7]: http://aka.ms/aprwiki -### Long Running Operations +### Long Running Operations & Jobs The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. @@ -684,15 +681,25 @@ The Microsoft REST API guidelines for Long Running Operations are an updated, cl ### Distributed Tracing & Service Telemetry +* Distributed Tracing +* Service Telemetry +* How to collect client side telemetry -### Jobs -* e.g. cascading delete ### Bring your own storage * Getting data into your service * Working with blobs ### Optimistic concurrency +* Conditional Access +* Optimistic Concurrency +* Etags + + +### Getting data into your service +* Bring your own storage +* Working with blobs + ## Final Thoughts / Summary * Careful consideration up front From 0fd4b303e99aad69299d9585c950a99fb8ab1faa Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 7 Jun 2021 21:44:26 -0500 Subject: [PATCH 102/729] fix typos --- azure/Guidelines.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 497c45a2..376bb5e8 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -68,7 +68,7 @@ Think about the code that a customer will write both before and after the REST A ### Focus on hero scenarios -It is important to realize that writing an API is, in many cases, the easist part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documenation, and creation of client libraries and examples.Focusing on hero scenarios reduces development, support, and maintenace costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A tell tale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. Service teams: +It is important to realize that writing an API is, in many cases, the easist part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, and creation of client libraries and examples.Focusing on hero scenarios reduces development, support, and maintenace costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A tell tale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. Service teams: :white_check_mark: **DO** define "hero scenarios" first, then the operations required, & then design the API @@ -77,8 +77,8 @@ It is important to realize that writing an API is, in many cases, the easist par :no_entry: **DO NOT** add APIs for speculative features customers might want ### Start with your API definition -Understanding how your service will be used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It should be reflect the naming decisions and make it easy for developers to implement your hero scenarios. -:white_check_mark: **DO** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes the service. The OpenAPI Specification is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. +Understanding how your service will be used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It should reflect the naming decisions and make it easy for developers to implement your hero scenarios. +:white_check_mark: **DO** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes the service. The OpenAPI Definition is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. :ballot_box_with_check: **YOU SHOULD** describe their services using ADL *[LINK TO ADL HERE]*. @@ -87,7 +87,8 @@ Understanding how your service will be used and defining its model and interacti ### Use previews to iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. -:ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. :ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. +:ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. +:ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. :ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. :ballot_box_with_check: **YOU SHOULD** consider doing a *code with* exercise in which you actively develop with the customer, observing and learning from their API usage. :ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. @@ -240,19 +241,20 @@ retry-after | Response | 180 [see Throttling Client Requests] *x-ms-error-code* | Response | [see Processing a REST Request](http://TODO:link-goes-here) Last-Modified | Response | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) -:white_check_mark: **DO** specify headers using kabob-casing. +:white_check_mark: **DO** specify headers using kebab-casing. :white_check_mark: **DO** compare request header names using case-insensitivity :white_check_mark: **DO** compare request header values using case-sensitivity. Some exceptions exist: user-agent?, accept?, content-type?, RFC1123 dates, guids?. -#### HTTP Methods & Idempotency -Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. -:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. In addition, each request / response header. +:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. In addition, each request / response header. :no_entry: **DO NOT** use "x-" prefix for headers, unless the header already exists in production. -:warning: **YOU SHOULD NOT** using the POST method unless you can guarantee it can be implemented idempotently. +#### HTTP Methods & Idempotency +Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. + +:warning: **YOU SHOULD NOT** use the POST method unless you can guarantee it can be implemented idempotently. :white_check_mark: **DO** validate all query parameter and request header values. TODO: What to return on failure :white_check_mark: **DO** return the state of the resource after a PUT, PATCH, or GET operation with a ```200-OK``` or ```201-Created```. @@ -290,7 +292,7 @@ Byte array | Base-64 encoded, max length * [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) ### REST -REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you will define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to retrieve and update the state of your resource. Note, it's important to model resource state, not to behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. +REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you will define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to retrieve and update the state of your resource. Note, it's important to model resource state, not behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. TODO: Add link to behavior section When designing your service, it is important to optimize for the developer using your API. @@ -315,7 +317,7 @@ TODO: Some fields may be marked as required indicating that their value must alw > NOTE: A service is not allowed to introduce new required fields or remove any required fields in newer versions of the service. -For PATCH, the their must be a similar JSON schema with no required fields nullable. +For PATCH, there must be a similar JSON schema with no required fields nullable. --- --- @@ -340,7 +342,7 @@ The following are general guidelines when using REST: :white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. :white_check_mark: **DO** use PUT with JSON for wholesale create/update operations. -> NOTE: If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent tof DELETE followed by PUT). +> NOTE: If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT). :white_check_mark: **DO** use DELETE to remove a resource. @@ -352,9 +354,9 @@ TODO: If we keep this NOTE, then it is about IDs, not about DELETE: NOTE: Ids ar :white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions but avoid if at all possible. -:no_entry: **DO** fail an operation with ```400-Bad Request``` if the request JSON body is improperly-formed JSON. +:white_check_mark: **DO** fail an operation with ```400-Bad Request``` if the request JSON body is improperly-formed JSON. -:no_entry: **DO** fail an operation with ```412-Unprocessable Entity``` if any JSON field name or value is not fully understood by the specific version of the service. +:white_check_mark: **DO** fail an operation with ```412-Unprocessable Entity``` if any JSON field name or value is not fully understood by the specific version of the service. :no_entry: **DO NOT** return secret fields via GET. For example, do not return adminPassword in JSON. @@ -503,7 +505,7 @@ A _breaking change_ is any change in the API that may cause client or service co :white_check_mark: **DO** increment the version of your API for _Compliance changes_. -:white_check_mark: **DO** review any breaking changes with the Azure REST API stewardship board prior to seecing approval through the [Azure Global Breaking Change Policy][7]. +:white_check_mark: **DO** review any breaking changes with the Azure REST API stewardship board prior to seeking approval through the [Azure Global Breaking Change Policy][7]. :white_check_mark: **DO** follow the [Azure Global Retirement Policy][7] for any deprecated APIs. @@ -537,7 +539,7 @@ This functionality is only available for single cloud deployments because the AP All situations where the API definition is changed (irrespective of whether a version change happens or not) - **MUST** be reviewed by the Azure REST API Review Board before release. + **MUST** be reviewed by the Azure REST API stewardship board before release. #### Preview Versions From 090281ac27c8252575cce2f5a3b595f8beb0df63 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 4 Jun 2021 16:18:31 +0000 Subject: [PATCH 103/729] some minor cleanup --- azure/Guidelines.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 376bb5e8..0768e992 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -129,9 +129,9 @@ Azure services will adhere to the HTTP specification, [RFC7231](https://tools.ie A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. These are so central to the developer experience that careful consideration should be given when devising your URL structure. ## URLs -A Uniform Resource Locator (URL) is how developers access your service's resources. The structure of the URL is critically important as it describes the service's cognitive model: +A Uniform Resource Locator (URL) is how developers access your service's resources. The structure of the URL is critical as it describes the service's cognitive model. -In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services. Specifically: +In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services. :white_check_mark: **DO** expose their service to developers via the following URL pattern: @@ -143,7 +143,6 @@ Where: * **service**: name of the service (ex: blobstore, servicebus, directory, or management) * **cloud**: cloud domain name (see Azure CLI's "az cloud list") - | Cloud | Domain | | ------------- | ----- | | Public | azure.net | From c14f45ea635d732da49edb4bcdadf5e55375f95c Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 4 Jun 2021 15:05:33 -0700 Subject: [PATCH 104/729] Updtae url/versioning guidance --- azure/Guidelines.md | 169 ++++++++++---------------------------------- 1 file changed, 36 insertions(+), 133 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 0768e992..f61d87e1 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -131,16 +131,12 @@ A Uniform Resource Locator (URL) is how developers will access the resources of ## URLs A Uniform Resource Locator (URL) is how developers access your service's resources. The structure of the URL is critical as it describes the service's cognitive model. -In addition to the [URL structure guidance](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#71-url-structure) in the Microsoft REST API guidelines, Azure has specific guidance about service exposure for multi-tenant services. - :white_check_mark: **DO** expose their service to developers via the following URL pattern: - ```text https://.///// ``` Where: - * **service**: name of the service (ex: blobstore, servicebus, directory, or management) * **cloud**: cloud domain name (see Azure CLI's "az cloud list") | Cloud | Domain | @@ -154,16 +150,14 @@ Where: * **service-root**: service-specific path (ex: blobcontainer, myqueue) -A service URL must be case-sensitive (except for scheme/host). If case doesn't match what you expect, the request __MUST__ fail with the appropriate HTTP return code. - -When returning information in a Response, services __MUST__ maintain and respect proper case values. +:white_check_mark: **DO** treat URLs as case-sensitive (except for scheme/host). If case doesn't match what you expect, the request __MUST__ fail with the appropriate HTTP return code. +> Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a GUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' -A well formed Azure service URL would look similar to the following: +:white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a response JSON body -TODO: Show URL example +:white_check_mark: **DO** return '414-URI Too Long' if a URL exceeds 2083 characters - -:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz) +:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) ### Direct endpoint URLs @@ -186,6 +180,11 @@ A direct endpoint URL may also be used for performance/routing: :white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. +:heavy_check_mark: **YOU MAY** use URLs as values +``` +https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy +``` + ### HTTP Request / Response Pattern The HTTP Request / Response pattern will dictate much of how your API behaves, for example; POST methods must be idempotent, GET methods may be cached, the If-Modified and etag headers determine your optimistic concurrency strategy. The URL of a service, along with its request / response, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you will make. For each request / response, the service: @@ -457,126 +456,37 @@ Overall, this is a very brittle design that leads to a poor developer experience ### API Versioning -Versioning is a powerful technique that helps you provide consistent and stable APIs to developers that use your service. The Microsoft REST API guidelines offer different options on how to specify an API version and guidance on what constitutes a breaking change. However, this section refines that guidance in order to drive greater consistency across the Azure services and stack, as well as the developer tooling, and client libraries. +Azure services need to change over time. However, when changing a service, there are 2 requirements: + 1. Already-running customer workloads must never break due to a service change + 2. Customers can adopt a new service version without requiring any code changes + - Of course, the customer must modify code to leverage any new service features -:white_check_mark: **DO** use explicit versioning for your service API. +:ballot_box_with_check: **DO** review any API changes with the Azure API Stewardship Board -:white_check_mark: **DO** follow the standard [_Azure Global Retirements and Breaking Changes_][7] when retiring your API. - -#### Specifying the version in Azure -The Microsoft REST API guidelines give two options for how services and clients communicate the version: a url segment and a query parameter. For consistency across the Azure portfolio, you must specify the version using a query parameter. - -Examples: +:white_check_mark: **DO** use an 'api-version' query parameter with a date ```text -GET https://blobstore.azure.com/foo.com/acct1/c1/blob2?api-version=1.0 -PUT https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2014-12-07 -POST https://blobstore.azure.com/foo.com/acct1/c1/b2?api-version=2015-12-07 +PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 ``` -#### API Changes that require a version change - -There are three groups of changes that may happen to an API. - -1. Changes made to an **EXISTING** API version due to security or compliance reasons. We shall refer to these types of changes as _Compliance changes_. -2. Changes made to an API that may cause a client making the API call to fail, such as removal of an endpoint or property or changing the format of the body. We refer to these types of changes as _Breaking changes_. -3. Additive changes made to an API that do not cause a client making the API call to fail, such as the addition of a new optional property or a new endpoint. We refer to these types of changes as _Evolutionary changes_. - -With the exception of _Compliance changes_ (which are extremely rare), Azure services must update the version number of their API whenever there is a change to the API, _no matter how small_. Customers will "lock the API version" so that their code does not fail when the service introduces new features and will rely on the fact that your API version is a contract with the services that will never change. - -A _breaking change_ is any change in the API that may cause client or service code making the API call to fail. Obvious examples of such a change are the removal of an endpoint, adding or removing a required field or changing the format of the body (from XML to JSON for example). Even though we recommend clients ignore new fields, there are many libraries and clients that fail when new fields are introduced. Removing an endpoint from an API is always a _breaking change_. Adding a new endpoint is always an _evolutionary change_. Changes to properties may be _evolutionary_ or _breaking_ depending on the type of change and whether the change is to an input parameter or output parameter: - -| Property change | Input | Output | -|:-----------------------|:------------:|:------------:| -| Remove a property | Breaking | Breaking | -| Add optional property | Evolutionary | Breaking | -| Add required property | Breaking | Breaking | -| Data type change | Breaking | Breaking | -| Format change | Breaking | Breaking | -| Integer widens | Evolutionary | Breaking | -| Integer narrows | Breaking | Evolutionary | -| Add new value to enum | Evolutionary | Breaking | -| Remove value from enum | Breaking | Breaking | -| Optional to required | Breaking | Breaking | -| Required to optional | Evolutionary | Breaking | - - -:ballot_box_with_check: **YOU SHOULD** use a date oriented semantic as the version numbering scheme of your API, e.g. `2021-05-01-preview.1`. - -:white_check_mark: **DO** increment the version of your API for _Compliance changes_. - -:white_check_mark: **DO** review any breaking changes with the Azure REST API stewardship board prior to seeking approval through the [Azure Global Breaking Change Policy][7]. - -:white_check_mark: **DO** follow the [Azure Global Retirement Policy][7] for any deprecated APIs. - -:white_check_mark: **DO** increment the major version (if using SemVer) for any breaking changes. - -:white_check_mark: **DO** increment the version bump for _evolutionary changes. If the service is using SemVer for versioning, evolutionary changes constitute a minor version change. - -:ballot_box_with_check: **YOU SHOULD** review any _evolutionary change_ with the Azure API stewardship board. - - -#### Changing the API without changing the version - -Because the API version represents a contract that a developer can rely on when generating SDKs to communicate with the service, there are a limited set of situations where changing the API is permissable without a version bump. The only changes universally allowed: - -1. Adding a new (optional) value to an extensible enum. - -An extensible enum is (in essence) a string. The values of the extensible enum drive intellisense and documentation, but the values are not considered exhaustive. - -If a service is **ONLY** available in the Azure public cloud, then an additional situation can be used to add functionality without changing the version: - -1. Adding a new (optional) query parameter to adjust the output (for example, adding filter options to a list operation). -2. Adding optional computed read-only output values that are generated based on the new (optional) query parameters. - -For example, let's say an image service wants to add bounding-box information to the output of an operation. The service can add a new query parameter `includeBoundingBox=true` and then include the bounding box information within the output only when the new query parameter is specified. A version bump is recommended, but not required. +:white_check_mark: **DO** use a later date for each new version -If not changing the API version, the service **MUST** update all data centers before the new query parameter is advertised to customers. - -:no_entry: **DO NOT** use this mechanism to get around the version bump. Adding such query parameters results in sub-optimal API designs and should only be used for exceptional circumstances. - -This functionality is only available for single cloud deployments because the API version specifies the contract with the developer. Consider, for example, if such a functionality was included in Azure public cloud and not a sovereign cloud. A developer creating an SDK based on this functionality may see the application work in one cloud but fail when targeting the other despite using the same API version in both cases. For the purposes of this situation, "other clouds" includes Azure Stack and other deployment mechanisms such as containers for on-premise usage. - - -All situations where the API definition is changed (irrespective of whether a version change happens or not) - **MUST** be reviewed by the Azure REST API stewardship board before release. - -#### Preview Versions - -Preview versions of the API can be indicated by adding the suffix `-preview.X` to the end of the API version, where `X` is an incrementing integer. For example: - -* `2021-05-01-preview.1` -* `1.0-preview.2` - -Preview versions are not treated the same way as release versions. In general, there are two types of previews: - -* **Private** previews are released to a known subset of users. The service team knows how to contact each person within the private preview. There are no restrictions on changes within a private preview, as long as the service team communicates effectively with their users on what changes are made and when they will be made. - -* **Public** previews are released broadly, but contain APIs that may change between previews and may be deleted prior to the final version. The `X` integer (indicating the revision of the preview) must be incremented for all breaking changes (resulting in a new API version). Evolutionary changes may be added as needed, as long as the change is communicated broadly. - -You should follow the axiom "don't surprise your customers" when deciding whether to increment the preview version, and err on the side of incrementing the preview version. - -Preview APIs [follow a different path for deprecation and retirement](https://dev.azure.com/msazure/AzureWiki/_wiki/wikis/AzureWiki.wiki/37683/Retirement-of-Previews), and are not subject to the same guarantees as APIs marked as generally available. Preview APIs have a life-span of not more than 12 months, after which they must be retired. - -#### Why Azure recommends conservative API versioning - -Azure history is replete with anecdotes that directly relate to API versioning. For instance, Cognitive Services unintentionally broke customers by making changes to the API structure without a version bump with updates that they did not think would be breaking changes. These changes led customers to question the stability and maturity of the product and increased the churn rate for the services. - -Even changes that are evolutionary can cause problems. For instance, let's say that a service adds a new feature via a new endpoint in the API. The SDK gets updates to support this new API, but the service does not bump the version number. Since the roll out of the new feature is not atomic, there is a period of time (potentially months long) where the feature is available in some regions but not others. A customer has the potential for attempting to use the feature in two different regions and having it work in one region but not the other, despite the two regions supporting the same version number. This is only made worse when we consider Azure Stack, which can be upwards of a year behind the public cloud offerings. - -There are a few mechanisms that can reduce breaking changes and their effects on our customers. - -#### Use PATCH instead of PUT for updates +:white_check_mark: **DO** suffix the date with '-preview' for a preview API +```text +PUT https://service.azure.com/users/Jeff?api-version=2021-06-04-preview +``` +> The only difference between a private and public preview is whether you publicly document the API; there is no impact to the 'api-version' value -The HTTP PUT verb is an idemopotent apply operation for the API version being used. The [Microsoft API Guidelines already recommend the use of PATCH](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#742-patch) for updates. +:no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview 'api-version' is '2021-06-04-preview', the GA version of the API must be a date later than 2021-06-04 -Consider the following sequence: +:white_check_mark: **DO** use a later date for each new preview version +> When releasing a new preview, the service team may completely retire any previous preview versions after giving customers at least 90 days to upgrade their code -* User1 creates a resource with version v2, using a new optional parameter. -* Later, User2 wants to update the resource using unrelated settings. Using version v1, User2 issues a GET, does the changes, and then issues a PUT to replace the resource definition. +:white_check_mark: **DO** use a later date for successive preview versions. -In this case, the optional parameter is lost because of the replace semantics. The optional parameter only exists on API version v2, and not on version v1. +:no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. -Service teams SHOULD prefer and recommend PATCH operations for updating resources. +:no_entry: **DO NOT** introduce any breaking changes into service. +> NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy/) has tables (section 5) describing what kinds of changes are considered breaking. If a new service version must break customers (due to security/compliance/etc.), contact the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) as soon as possible. #### Use extensible enums @@ -599,11 +509,7 @@ While removing a value from an enum is a breaking change, adding an enum can be } ``` -Always model an enum as a string unleess you are positive that the symbol set will **NEVER** change over time. - -#### Group versioning in Azure and Azure Stack - -Azure Stack allows customers and hosters to deploy their own small versions of Azure and upgrade it at a different pace than Azure. In order to make it possible to write an application or SDK that targets both Azure and Azure Stack, additional versioning policy is necessary. Contact the Azure Stack team for further guidance. +Always model an enum as a string unless you are positive that the symbol set will **NEVER** change over time. #### Version discovery @@ -614,14 +520,19 @@ API version discovery is needed when either a given hosted service may expose a :white_check_mark: **DO** support API version discovery, including 1. Support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service + 2. Include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. + 3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it must include those versions (group and major.minor) in the `api-deprecated-versions` header. + 4. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. :ballot_box_with_check: **YOU SHOULD** support the following for version discovery: 1. In addition to the functionality described here, services should support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. + 2. Services should allow unauthenticated HTTP `OPTIONS` requests. When doing so, authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. + 3. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request should return the group versions that are supported across the expanded set. Example request to discover API versions (blob storage container list API): @@ -642,14 +553,6 @@ Content-Length: 0 Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. -#### Retiring pre-release and beta APIs - -Pre-release and beta APIs are not covered by the Azure Global Retirement and Deprecation Policy. Each team providing a preview API should communicate to customers what the policy is going to be for support and deprecation, even if that policy is “we may remove this at any time”. The Azure REST API Guidelines cover pre-release API versions. To summarize that section, they should be marked with a version tag like `2013-03-21-Preview`. Despite their unstable nature and the fact they are not officially supported, **customers have suffered downtime when we have deprecated preview APIs**. - -:ballot_box_with_check: **YOU SHOULD** should monitor preview endpoints closely. - -:ballot_box_with_check: **YOU SHOULD** consider following the normal deprecation policy. - ##### Additional References From 7f4717fb5b0b9e6ceec8c2920d6fdf0ebce8af85 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Wed, 16 Jun 2021 13:59:31 -0400 Subject: [PATCH 105/729] First pass at storage & updated error handling. --- azure/Guidelines.md | 153 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 133 insertions(+), 20 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index f61d87e1..3b51dd7d 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -307,18 +307,12 @@ When designing your service, it is important to optimize for the developer using #### JSON Resource Schema & Field Mutability For a given URL path, the JSON schema (data type) should be the same for PATCH, PUT, GET, DELETE, and GETting collection items. This allows one SDK type for input/output operations and enables the response to be passed back in request. While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. -:white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH request/response, GET response, and POST response. This allows one SDK type for input/output operations and enables the response to be passed back in request. - ---- -Jeff: What to say about required fields - it not really a REST thing -TODO: Some fields may be marked as required indicating that their value must alway be sent and is always returned. +:white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH request/response, GET response, and POST response. This allows one SDK type for input/output operations and enables the response to be passed back in request. > NOTE: A service is not allowed to introduce new required fields or remove any required fields in newer versions of the service. For PATCH, there must be a similar JSON schema with no required fields nullable. ---- ---- This is also not really a rest thing; more of a service implementation thing While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. REMOVE?: Tools like ADL do allow annotation of mutability, enabling more sophisticated code generation of client libraries. @@ -382,10 +376,38 @@ PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity PUT | Overwrite resource entirely using Create/Update fields | 200-OK #### Handling Errors +When your service encounters an error, you will not be able to return the payload that was sent as part of the operation. Because you cannot put a resource in the response, you will instead use a specific header, ```x-ms-error-code``` along with a string code. In addition, the message body will have the descriptive text of the error. This error message should give enough information to the customer so they can self-diagnose the problem. It is preferrable to include additional information as part the 'inner-error'. Informative error codes and messages increase the ability for customers to be successful and lowers the overall support costs for your service. The code value that is passed in the header is also repeated as the ```code``` value in the inner-error. It is possible that clients can recover from errors gracefully at runtime. Often, the mechanism employed will be to inspect the header value and implement appropriate coping logic. Because of this, the error code, is considered part of your API contract. Example: + +**HEADER** + +```x-ms-error-code``` : ```InvalidPasswordFormat``` + +**RESPONSE BODY** +```json +{ + "error": { + "code": "InvalidPasswordFormat", + "message": "Human-readable description", + "target": "target of error", + "innererror": { + "code": "PasswordTooShort", + "minLength": 6, + } + } +} +``` + +:white_check_mark: **DO** Return x-ms-error-code header with string + +:white_check_mark: **DO** return an ```error``` as part of the response body. The 1st ```code``` must match the ```x-ms-error-code```. + +:white_check_mark: **DO** document runtime errors that are recoverable. + +:no_entry: **DO NOT** change the value of ```code``` between versions--it is part of your API contract and is considered a breaking change. + +:heavy_check_mark: **YOU MAY** change the values of all other fields. -:white_check_mark: **DO** return x-ms-error-code header with string -:white_check_mark: **DO** ensure your service returns the error response body (TODO: show it here) ### JSON Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: @@ -582,8 +604,6 @@ The Microsoft REST API guidelines for Long Running Operations are an updated, cl :white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. - - ### Distributed Tracing & Service Telemetry * Distributed Tracing * Service Telemetry @@ -591,18 +611,111 @@ The Microsoft REST API guidelines for Long Running Operations are an updated, cl ### Bring your own storage -* Getting data into your service -* Working with blobs +When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS will provide the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders, nor do they have extensions. + +:white_check_mark: **DO** use Azure Bring Your Own Storage. +:no_entry: **DO NOT** require a fresh container per operation (there's a limit of 50K so that approach doesn't scale to tons of usage) - :white_check_mark: **DO** use a blob prefix instead +#### Authentication +How you secure and protect the data and files that your service uses will not only affect how consumable your API is, but also, how quickly you can evolve and adapt it. Implementing Role Based Access Control [RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) is the recommended approach. It is important to recognize that any roles defined in RBAC essentially become part of your API contract. For example, changing a role's permissions, e.g. restricting access, could effectively cause existing clients to break, as they may no longer have access to necessary resources. + +:white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions + +:white_check_mark: **DO** Ensure that RBAC roles MUST are backward compatible, and specifically, you cannot take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. + +##### Handlilng 'downstream' errors +It is not uncommon to rely on other services, e.g. storage, when implementing your service. Inevitably, the services you depend on will fail. In these situations, you can include the downstream erorr code and text in the inner-error of the response body. This provides a consistent pattern for handling errors in the services you depend upon. + +:white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. + + TODO: There are some security considerations here, e.g. returning the endpoint/url with a status code that exists/doesn't exist. ) + +#### Working with files +Generally speaking, there are two patterns that you will encounter when working with files; single file access, and file collections. + +##### Single file access +Desiging an API for accessing a single file, depending on your scenario, is relatively straight forward. + +:heavy_check_mark: **YOU MAY** use a Shared Access Signature [SaS](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) to provide access to a single file. SaS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. + +:ballot_box_with_check: **YOU SHOULD** if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text. + +:ballot_box_with_check: **YOU SHOULD** support managed identity using Azure Storage by default (if using Azure services). + +###### File versioning +Depending on your requirements, there are scenarios where users of your service will require a specific version of a file. For example, you may need to keep track of configuration changes over time to be able to rollback to a previous state. In these scenarios, you will need to provide a mechanism for accessing a specific version. + +:white_check_mark: **DO** Enable the customer to provide an ETag to specify a specific version of a file. + + + +TODO: collection of files +
+Azure storage vs. something else + + + + +### Conditional Requests + +Avoid pessimistic strategies, e.g. last writer wins +- Prefer optimistic concurrency control. Last writer wins may be sufficient. It’s very expensive to build and scale pessimistic concurrency control (locks, leases, etc.) + + +SHOULD: Conditional read (cache validation) +- Conditional GETs improves performance by allowing cache validation + + +Conditionaly updates (You SHOULD use optimistic concurrency) +- Conditional PUT/POST/DELETE/PATCH allow optimistic concurrency +This is a strong recommendation. +Services SHOULD force conditional updates by providing a 428 return value +(Review w/team) + +#### Optimistic concurrency +SHOULD Always return an ETag with any operation returning the resource or part of a resource (this includes getting, updating that returns a resource, listing that returns partial resources, etc.) or any update of the resource (whether the resource is returned or not). +Always return an etag. +If you return an etag, you must always accept an etag on all other operations. + + +#### Computing ETags +Strategy of how you compute the etag depends on the semantic of the etag. +Resources that are inherently versioned, should have the version. Otherwise hash. + + +etag - hash of the value +SHOULD optionally use a hash of the resource, but you must hash the entire resource and this can be expensive to compute. Especially for for listing operations. Apache uses file system info like file size + last write to generate the ETag. This can work in some cases, but make sure it's not dependent on anything specific to the server sending the response + +Versioning semantic. +MAY Best option is to add a timestamp and version identifier in your resource schema. +Timestamp shouldn't be returned with more than subsecond precision if you'll also be using the Last-Modified HTTP response header (or sub-millisecond otherwise per our general guidance).SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. + + +MAY consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes. You can also use weak etags if it's expensive to compute an ETag (i.e., weak etag is size in bytes which might not be super accurate compared to an MD5 hash of a sizeable resource). +If you choose to use a Weak ETag, then... +(add text from mike) +(Review w/team, e.g. when to use in Azure) + +#### Multiple conditions: If-Match && If-Unmodified-Since && (If-None-Match || If-Modified-Since) +o If you have multiple conditions fail, return the most severe status code +see https://docs.microsoft.com/en-us/rest/api/storageservices/specifying-conditional-headers-for-blob-service-operations for examples + +You MAY support preflight requests... +Preflight requests? Often supported for CORS but could be used for any potentially expensive request. Consider "EXPECT: 100-continue" for other requests that will return 100 Continue if the conditions are satisfactory or 417 Expectation Failed otherwise. Useful for conditionaly requests with large payloads. You should return the same error code that the service should use. +SHOULD provide documentation on what preflight checks will be validated. + +AWS uses this for S3 API + + +- Status codes +o GET: if the comparison fails, return 304 Not Modified (consider also returning Expires/Cache-Control/Age headers for caching scenarios) +o PUT/POST/DELETE: if the comparison fails, return 412 Precondition Not Met +o Consider forcing conditional headers on resource mutation, then use 428 Precondition Required if they're not present. + +- If-Match header - should support multiple values that are Or-ed together per HTTP/1.1. + -### Optimistic concurrency -* Conditional Access -* Optimistic Concurrency -* Etags -### Getting data into your service -* Bring your own storage -* Working with blobs ## Final Thoughts / Summary From 5e6648012b155c5ed237205e8a3ee26d41a99786 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Thu, 17 Jun 2021 18:22:56 -0400 Subject: [PATCH 106/729] Updated BYOS to include multiple files. --- azure/Guidelines.md | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 3b51dd7d..138e7309 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -224,7 +224,7 @@ Header Key | Applies to | Example *authorization* | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) *x-ms-useragent* | Request | [see Telemetry](http://TODO:link-goes-here) traceparent | Request | [see Distributed Tracing]](http://TODO:link-goes-here) -tracecontext | Request | [see Distributed Tracing](http://TODO:link-goes-here) +tracecontext | Request | [see Distributed Tracing](http://TODO:link-goes-here)++ accept | Request | application/json if-match | Request | "67ab43" or * (no quotes) (see Conditional Access) if-none-match | Request | "67ab43" or * (no quotes) [see Conditional Access](http://TODO:link-goes-here) @@ -237,7 +237,7 @@ date [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123) | Both | Sun, 06 N etag | Response | "67ab43" [see Conditional Access](http://TODO:link-goes-here) retry-after | Response | 180 [see Throttling Client Requests] *x-ms-error-code* | Response | [see Processing a REST Request](http://TODO:link-goes-here) -Last-Modified | Response | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) +*Last-Modified* | Response | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) :white_check_mark: **DO** specify headers using kebab-casing. @@ -614,7 +614,8 @@ The Microsoft REST API guidelines for Long Running Operations are an updated, cl When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS will provide the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders, nor do they have extensions. :white_check_mark: **DO** use Azure Bring Your Own Storage. -:no_entry: **DO NOT** require a fresh container per operation (there's a limit of 50K so that approach doesn't scale to tons of usage) - :white_check_mark: **DO** use a blob prefix instead +:no_entry: **DO NOT** require a fresh container per operation +:white_check_mark: **DO** use a blob prefix instead #### Authentication How you secure and protect the data and files that your service uses will not only affect how consumable your API is, but also, how quickly you can evolve and adapt it. Implementing Role Based Access Control [RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) is the recommended approach. It is important to recognize that any roles defined in RBAC essentially become part of your API contract. For example, changing a role's permissions, e.g. restricting access, could effectively cause existing clients to break, as they may no longer have access to necessary resources. @@ -627,7 +628,7 @@ It is not uncommon to rely on other services, e.g. storage, when implementing yo :white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. - TODO: There are some security considerations here, e.g. returning the endpoint/url with a status code that exists/doesn't exist. ) + TODO: There are some security considerations here, e.g. returning the endpoint/url with a status code that exists/doesn't exist. Johan ) #### Working with files Generally speaking, there are two patterns that you will encounter when working with files; single file access, and file collections. @@ -635,7 +636,7 @@ Generally speaking, there are two patterns that you will encounter when working ##### Single file access Desiging an API for accessing a single file, depending on your scenario, is relatively straight forward. -:heavy_check_mark: **YOU MAY** use a Shared Access Signature [SaS](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) to provide access to a single file. SaS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. +:heavy_check_mark: **YOU MAY** use a Shared Access Signature [SAS](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. :ballot_box_with_check: **YOU SHOULD** if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text. @@ -645,15 +646,43 @@ Desiging an API for accessing a single file, depending on your scenario, is rela Depending on your requirements, there are scenarios where users of your service will require a specific version of a file. For example, you may need to keep track of configuration changes over time to be able to rollback to a previous state. In these scenarios, you will need to provide a mechanism for accessing a specific version. :white_check_mark: **DO** Enable the customer to provide an ETag to specify a specific version of a file. +##### File collections +When your users need to work with multiple files, for example a document translation service, it will be important to provide them access to the collection, and it's contents, in a consistent manner. Because there is no industry standard for working with with containers, these guidelines will recommend that you leverage Azure Storage. Following the guidelines above, you also want to ensure that you don't expose file system constructs, e.g. folders, and instead use storage constructs, e.g. blob prefixes. +:white_check_mark: **DO** When using a Shared Access Signature (SAS), ensure this is assigned to the container and that the permissions apply to the content as well. +:white_check_mark: **DO** When using managed identity, ensure the customer has given the proper permissions to access the file container to the service. -TODO: collection of files -
-Azure storage vs. something else +A common pattern when working with multiple files is for your service to receive requests that contain the location(s) of files to process, e.g. "input" and a location(s) to place the any files that result from processing, e.g. "output." (Note: the terms "input" and "output" are just examples and terms more relevant to the service domain are more appropriate.) + +For example, in a request payload may look similar to the following: + +```json +{ +"input":{ + "location": "https://mycompany.blob.core.windows.net/documents/english/?", + "delimiter":"/" + }, +"output":{ + "location": "https://mycompany.blob.core.windows.net/documents/spanglish/?", + "delimiter":"/" + } +} +``` +Note: How the service gets the request body is outside the purview of these guidelines. + +Depending on the requirements of the service, there can be any number of "input" and "output" sections, including none. However, for each of the "input" sections the following apply: + +:white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter." +:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```listing``` and ```read``` permissions. +For each of the "output" sections the following apply: +:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```write``` permissions + + + TODO: Add the proper links for 'additional references' ) ### Conditional Requests From d8cc4fb04208d94804481d9d08f3612b2fac70aa Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 17 Jun 2021 08:47:18 -0700 Subject: [PATCH 107/729] Add guidance for Collections --- azure/Guidelines.md | 204 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 138e7309..31c1f170 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -476,6 +476,210 @@ Overall, this is a very brittle design that leads to a poor developer experience ### Collections +The standard REST pattern for accessing a collection of resources is with a _GET_ method on the collection URL. +This operation is commonly called the "list operation" for the resource, since the primary content of the response +is an array (list) of the resource type. Query parameters on the list operation may be provided to control +which resources are returned and in what order. + +Resource collections can often be arbitrarily large, increasing lookup time as well as the size of the responses being sent over the wire. Therefore, it is important that list operations implement pagination. + +:white_check_mark: **DO** provide a list operation for each resource type. + +:white_check_mark: **DO** structure the response to a list operation as an object with a top-level array property to contain the collection of resources. + +:ballot_box_with_check: **YOU SHOULD** use _value_ as the name of the top-level array property -- there are some allowable exceptions to this guidance. + +:white_check_mark: **DO** implement pagination of list operation responses unless there is no possibility of a collection exceeding a size appropriate for a single response. +In particular, include a top-level property in the response named _nextLink_ that contains an opaque URL to return the next page of results when there are additional items in the collection. + +:no_entry: **DO NOT** include a _nextLink_ property in the response if there are no additional items in the collection. + +:white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this. + +Clients MUST be resilient to collection data being either paged or nonpaged for any given request. + +Example: +```json +{ + "value":[ + { "id": "Item 1","price": 99.95,"sizes": null}, + { … }, + { … }, + { "id": "Item 99","price": 59.99,"sizes": null} + ], + "nextLink": "{opaqueUrl}" +} +``` + +#### Query options + +A number of query parameters that may be supported on a list operation to control which resources are returned and in what order. A summary of these query parameters is shown in the following table, with more detail given below. All these query parameters are optional. + +| Parameter name | type | description | +| -------------- | ---- | ----------- | +| _filter_ | string | an expression on the resource type that selects the resources to be returned | +| _orderby_ | array of string | a list of expressions that specify the order of the returned resources | +| _skip_ | integer | an offset into the collection of the first resource to be returned | +| _maxpagesize_ | integer | the maximum number of resources to include in a single response | + +:white_check_mark: **DO** treat these query parameter names as case-sensitve. + +:no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). + +:no_entry: **DO NOT** define a _top_ query parameter to limit the number of results returned. Use the _maxpagesize_ parameter for this purpose. + +:white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. + +:white_check_mark: **DO** apply _select_ or _expand_ options after applying all the query options in the table above. + +#### filter + +:heavy_check_mark: **YOU MAY** support filtering of the results of a list operation with the _filter_ query parameter. + +The value of the _filter_ option is a Boolean expression which is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. + +:white_check_mark: **DO** omit all resources from the collection for which the _filter_ expression evaluates to false or to null, or references properties that are unavailable due to permissions. + +Example: return all Products whose Price is less than $10.00 + +```http +GET https://api.contoso.com/v1.0/products?filter=price lt 10.00 +``` + +##### filter operators + +:ballot_box_with_check: **YOU SHOULD** support the following minimal set of operators in _filter_ expressions. + +Operator | Description | Example +-------------------- | --------------------- | ----------------------------------------------------- +Comparison Operators | | +eq | Equal | city eq 'Redmond' +ne | Not equal | city ne 'London' +gt | Greater than | price gt 20 +ge | Greater than or equal | price ge 10 +lt | Less than | price lt 20 +le | Less than or equal | price le 100 +Logical Operators | | +and | Logical and | price le 200 and price gt 3.5 +or | Logical or | price le 3.5 or price gt 200 +not | Logical negation | not price le 3.5 +Grouping Operators | | +( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 + +:white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if a client includes an operator in a _filter_ expression that is not supported by the operation. + +:white_check_mark: **DO** use the following operator precedence for supported operators when evaluating _filter_ expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence: + +| Group | Operator | Description | +|:----------------|:---------|:----------------------| +| Grouping | ( ) | Precedence grouping | +| Unary | not | Logical Negation | +| Relational | gt | Greater Than | +| | ge | Greater than or Equal | +| | lt | Less Than | +| | le | Less than or Equal | +| Equality | eq | Equal | +| | ne | Not Equal | +| Conditional AND | and | Logical And | +| Conditional OR | or | Logical Or | + +##### Operator examples +The following examples illustrate the use and semantics of each of the logical operators. + +Example: all products with a name equal to 'Milk' + +```http +GET https://api.contoso.com/v1.0/products?filter=name eq 'Milk' +``` + +Example: all products with a name not equal to 'Milk' + +```http +GET https://api.contoso.com/v1.0/products?filter=name ne 'Milk' +``` + +Example: all products with the name 'Milk' that also have a price less than 2.55: + +```http +GET https://api.contoso.com/v1.0/products?filter=name eq 'Milk' and price lt 2.55 +``` + +Example: all products that either have the name 'Milk' or have a price less than 2.55: + +```http +GET https://api.contoso.com/v1.0/products?filter=name eq 'Milk' or price lt 2.55 +``` + +Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: + +```http +GET https://api.contoso.com/v1.0/products?filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 +``` + +#### orderby + +:heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the _orderby_ query parameter. + +The value of the _orderby_ parameter is a comma-separated list of expressions used to sort the items. +A special case of such an expression is a property path terminating on a primitive property. + +Each expression in the _orderby_ parameter value may include the suffix "asc" for ascending or "desc" for descending, separated from the expression by one or more spaces. + +:white_check_mark: **DO** sort the collection in ascending order on an expression if "asc" or "desc" is not specified. + +:white_check_mark: **DO** sort NULL values as "less than" non-NULL values. + +:white_check_mark: **DO** sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on. + +:white_check_mark: **DO** use the inherent sort order for the type of the property. For example, date-time values should be sorted chronologically and not alphabetically. + +:white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if the client requests sorting by a property that is not supported by the operation. + +For example, to return all people sorted by name in ascending order: + +```http +GET https://api.contoso.com/v1.0/people?$orderBy=name +``` + + +For example, to return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. + +```http +GET https://api.contoso.com/v1.0/people?$orderBy=name desc,hireDate +``` + +Sorting MUST compose with filtering such that: + +```http +GET https://api.contoso.com/v1.0/people?filter=name eq 'david'&orderby=hireDate +``` + +will return all people whose name is David sorted in ascending order by hireDate. + +##### Considerations for sorting with pagination + +:white_check_mark: **DO** use the same filtering options and sort order for all pages of a paginated list operation response. + +##### skip + +:heavy_check_mark: **YOU MAY** allow clients to pass the _skip_ query parameter to specify an offset into collection of the first resource to be returned. + +:white_check_mark: **DO** define the _skip_ parameter as an integer with a default and minimum value of 0. + +##### maxpagesize + +:ballot_box_with_check: **YOU SHOULD** allow clients to pass the _maxpagesize_ query parameter to specify the maximum number of resources to include in the response. + +:white_check_mark: **DO** define the _maxpagesize_ parameter as an optional integer with a default value appropriate for the collection. + +:white_check_mark: **DO** make clear in documentation of the _maxpagesize_ parameter that the operation may choose to return fewer resources than the value specified in a single response. + +:white_check_mark: **DO** apply any _skip_ value specified by the client before applying the _maxpagesize_ to the collection. + +> MDK: Make sure that the guidance below is captured elsewhere then remove from here. +> Note: If the server can't honor _skip_ or _maxpagesize_ (e.g. a negative value is specified), the server MUST return an error to the client informing about it instead of just ignoring the query options. +> This will avoid the risk of the client making assumptions about the data returned. + ### API Versioning Azure services need to change over time. However, when changing a service, there are 2 requirements: From 1e3909812aef4e618005b8bfa3ea7180ddfddace Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sat, 19 Jun 2021 07:01:10 -0700 Subject: [PATCH 108/729] Updates from second review --- azure/Guidelines.md | 54 +++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 31c1f170..ef9ab258 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -485,19 +485,20 @@ Resource collections can often be arbitrarily large, increasing lookup time as w :white_check_mark: **DO** provide a list operation for each resource type. -:white_check_mark: **DO** structure the response to a list operation as an object with a top-level array property to contain the collection of resources. +:white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field to contain the collection of resources. -:ballot_box_with_check: **YOU SHOULD** use _value_ as the name of the top-level array property -- there are some allowable exceptions to this guidance. +:ballot_box_with_check: **YOU SHOULD** at a minimum include the id field in each resource. Also include the _ETag_ if supported for this resource type. + +:ballot_box_with_check: **YOU SHOULD** use _value_ as the name of the top-level array field -- there are some allowable exceptions to this guidance. :white_check_mark: **DO** implement pagination of list operation responses unless there is no possibility of a collection exceeding a size appropriate for a single response. -In particular, include a top-level property in the response named _nextLink_ that contains an opaque URL to return the next page of results when there are additional items in the collection. +In particular, include a top-level field in the response named _nextLink_ that contains an opaque absolute URL to return the next page of results when there are additional items in the collection. -:no_entry: **DO NOT** include a _nextLink_ property in the response if there are no additional items in the collection. +:no_entry: **DO NOT** include a _nextLink_ field in the response if there are no additional items in the collection. + :white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this. -Clients MUST be resilient to collection data being either paged or nonpaged for any given request. - Example: ```json { @@ -520,14 +521,13 @@ A number of query parameters that may be supported on a list operation to contro | _filter_ | string | an expression on the resource type that selects the resources to be returned | | _orderby_ | array of string | a list of expressions that specify the order of the returned resources | | _skip_ | integer | an offset into the collection of the first resource to be returned | +| _top_ | integer | the maximum number of resources to return from the collection | | _maxpagesize_ | integer | the maximum number of resources to include in a single response | :white_check_mark: **DO** treat these query parameter names as case-sensitve. :no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). -:no_entry: **DO NOT** define a _top_ query parameter to limit the number of results returned. Use the _maxpagesize_ parameter for this purpose. - :white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. :white_check_mark: **DO** apply _select_ or _expand_ options after applying all the query options in the table above. @@ -536,14 +536,14 @@ A number of query parameters that may be supported on a list operation to contro :heavy_check_mark: **YOU MAY** support filtering of the results of a list operation with the _filter_ query parameter. -The value of the _filter_ option is a Boolean expression which is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. +The value of the _filter_ option is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response. :white_check_mark: **DO** omit all resources from the collection for which the _filter_ expression evaluates to false or to null, or references properties that are unavailable due to permissions. Example: return all Products whose Price is less than $10.00 ```http -GET https://api.contoso.com/v1.0/products?filter=price lt 10.00 +GET https://api.contoso.com/products?filter=price lt 10.00 ``` ##### filter operators @@ -589,31 +589,31 @@ The following examples illustrate the use and semantics of each of the logical o Example: all products with a name equal to 'Milk' ```http -GET https://api.contoso.com/v1.0/products?filter=name eq 'Milk' +GET https://api.contoso.com/products?filter=name eq 'Milk' ``` Example: all products with a name not equal to 'Milk' ```http -GET https://api.contoso.com/v1.0/products?filter=name ne 'Milk' +GET https://api.contoso.com/products?filter=name ne 'Milk' ``` Example: all products with the name 'Milk' that also have a price less than 2.55: ```http -GET https://api.contoso.com/v1.0/products?filter=name eq 'Milk' and price lt 2.55 +GET https://api.contoso.com/products?filter=name eq 'Milk' and price lt 2.55 ``` Example: all products that either have the name 'Milk' or have a price less than 2.55: ```http -GET https://api.contoso.com/v1.0/products?filter=name eq 'Milk' or price lt 2.55 +GET https://api.contoso.com/products?filter=name eq 'Milk' or price lt 2.55 ``` Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: ```http -GET https://api.contoso.com/v1.0/products?filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 +GET https://api.contoso.com/products?filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 ``` #### orderby @@ -631,27 +631,27 @@ Each expression in the _orderby_ parameter value may include the suffix "asc" fo :white_check_mark: **DO** sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on. -:white_check_mark: **DO** use the inherent sort order for the type of the property. For example, date-time values should be sorted chronologically and not alphabetically. +:white_check_mark: **DO** use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically. -:white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if the client requests sorting by a property that is not supported by the operation. +:white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if the client requests sorting by a field that is not supported by the operation. For example, to return all people sorted by name in ascending order: ```http -GET https://api.contoso.com/v1.0/people?$orderBy=name +GET https://api.contoso.com/people?$orderBy=name ``` For example, to return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. ```http -GET https://api.contoso.com/v1.0/people?$orderBy=name desc,hireDate +GET https://api.contoso.com/people?$orderBy=name desc,hireDate ``` Sorting MUST compose with filtering such that: ```http -GET https://api.contoso.com/v1.0/people?filter=name eq 'david'&orderby=hireDate +GET https://api.contoso.com/people?filter=name eq 'david'&orderby=hireDate ``` will return all people whose name is David sorted in ascending order by hireDate. @@ -666,9 +666,19 @@ will return all people whose name is David sorted in ascending order by hireDate :white_check_mark: **DO** define the _skip_ parameter as an integer with a default and minimum value of 0. +##### top + +:heavy_check_mark: **YOU MAY** allow clients to pass the _top_ query parameter to specify the maximum number of resources to return from the collection. + +:white_check_mark: **DO** define the _top_ parameter as an integer with a minimum value of 1. + +:white_check_mark: **DO** return all resources of the collection, starting from _skip_ if specified, possibly paginated by server-driven pagination, if _top_ is not specified. + +:white_check_mark: **DO** return the _top_ resources, accumulated over all server-driven paging, if the collection contains at least _top_ number of resources. Returning fewer than _top_ resources can be interpreted by the client to mean that there are no more resources in the collection. + ##### maxpagesize -:ballot_box_with_check: **YOU SHOULD** allow clients to pass the _maxpagesize_ query parameter to specify the maximum number of resources to include in the response. +:heavy_check_mark: **YOU MAY** allow clients to pass the _maxpagesize_ query parameter to specify the maximum number of resources to include in a single response. :white_check_mark: **DO** define the _maxpagesize_ parameter as an optional integer with a default value appropriate for the collection. @@ -676,6 +686,8 @@ will return all people whose name is David sorted in ascending order by hireDate :white_check_mark: **DO** apply any _skip_ value specified by the client before applying the _maxpagesize_ to the collection. +:no_entry: **DO NOT** support both _top_ and _maxpagesize_ unless server-side paging provides stronger consistency guarantees and these are described in the documentation. + > MDK: Make sure that the guidance below is captured elsewhere then remove from here. > Note: If the server can't honor _skip_ or _maxpagesize_ (e.g. a negative value is specified), the server MUST return an error to the client informing about it instead of just ignoring the query options. > This will avoid the risk of the client making assumptions about the data returned. From 96853ab4e1e60a3586b3717a17fb7a888abee70a Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 18 Jun 2021 09:29:36 -0400 Subject: [PATCH 109/729] A bit of cleanup in computing etag section --- azure/Guidelines.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ef9ab258..62cb9d85 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -936,8 +936,10 @@ Timestamp shouldn't be returned with more than subsecond precision if you'll als MAY consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes. You can also use weak etags if it's expensive to compute an ETag (i.e., weak etag is size in bytes which might not be super accurate compared to an MD5 hash of a sizeable resource). + If you choose to use a Weak ETag, then... (add text from mike) + (Review w/team, e.g. when to use in Azure) #### Multiple conditions: If-Match && If-Unmodified-Since && (If-None-Match || If-Modified-Since) @@ -948,21 +950,15 @@ You MAY support preflight requests... Preflight requests? Often supported for CORS but could be used for any potentially expensive request. Consider "EXPECT: 100-continue" for other requests that will return 100 Continue if the conditions are satisfactory or 417 Expectation Failed otherwise. Useful for conditionaly requests with large payloads. You should return the same error code that the service should use. SHOULD provide documentation on what preflight checks will be validated. -AWS uses this for S3 API - - Status codes -o GET: if the comparison fails, return 304 Not Modified (consider also returning Expires/Cache-Control/Age headers for caching scenarios) -o PUT/POST/DELETE: if the comparison fails, return 412 Precondition Not Met -o Consider forcing conditional headers on resource mutation, then use 428 Precondition Required if they're not present. +* GET: if the comparison fails, return 304 Not Modified (consider also returning Expires/Cache-Control/Age headers for caching scenarios) +* PUT/POST/DELETE: if the comparison fails, return 412 Precondition Not Met +* Consider forcing conditional headers on resource mutation, then use 428 Precondition Required if they're not present. - If-Match header - should support multiple values that are Or-ed together per HTTP/1.1. - - - - ## Final Thoughts / Summary * Careful consideration up front * Long term decisions that are often codified in SDKs, CODE, etc. From 2bbed1fda7d92dce7721a8314eecebb4ac657f64 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 18 Jun 2021 10:04:14 -0400 Subject: [PATCH 110/729] Added section on DT & Telemetry --- azure/Guidelines.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 62cb9d85..4e9f8184 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -959,6 +959,21 @@ SHOULD provide documentation on what preflight checks will be validated. - If-Match header - should support multiple values that are Or-ed together per HTTP/1.1. +### Distributed Tracing & Telemetry +Azure Core guidelines specify that you support telemetry data through both the ```User-Agent``` header, ```X-MS-UserAgent``` header, and Open Telemetry. +When a client library is created, code to handle telemetry and distributed tracing information will be automatically added. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. + +:white_check_mark: **DO** follow the Azure Core guidelines for supporting telemetry headers and Open Telemetry. + +:no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. + +#### Additional References +* [Azure Core Guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) +* [Azure User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) +* [Azure Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) +* [Open Telemetry](https://opentelemetry.io/) + + ## Final Thoughts / Summary * Careful consideration up front * Long term decisions that are often codified in SDKs, CODE, etc. From ae778184fe85cf1881b7e6205d06b1826a8238c2 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Mon, 21 Jun 2021 09:51:29 -0700 Subject: [PATCH 111/729] Update azure/Guidelines.md Co-authored-by: tg-msft --- azure/Guidelines.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 4e9f8184..541bd3b3 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -960,17 +960,17 @@ SHOULD provide documentation on what preflight checks will be validated. ### Distributed Tracing & Telemetry -Azure Core guidelines specify that you support telemetry data through both the ```User-Agent``` header, ```X-MS-UserAgent``` header, and Open Telemetry. -When a client library is created, code to handle telemetry and distributed tracing information will be automatically added. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. +Azure SDK client guidelines specify that client libraries must send telemetry data through the ```User-Agent``` header, ```X-MS-UserAgent``` header, and Open Telemetry. +Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. -:white_check_mark: **DO** follow the Azure Core guidelines for supporting telemetry headers and Open Telemetry. +:white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. :no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. #### Additional References -* [Azure Core Guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) -* [Azure User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) -* [Azure Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) +* [Azure SDK client guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) +* [Azure SDK User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) +* [Azure SDK Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) * [Open Telemetry](https://opentelemetry.io/) From 6aaa264d9cb8f66909b7602e21c949fc2062c5cc Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 22 Jun 2021 10:44:11 -0400 Subject: [PATCH 112/729] Adding CODEOWNERS to facilitate review assignment --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..cea7cb05 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# These are the set of folks who should review PRs on the azureRestUpdates branch. +* @markweitzel @jeffreyr @tg-msft @teglaza @mikekistler @johanste \ No newline at end of file From 098a5e224b4a0fccc56bd23e90f3ce559cd56b0c Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 22 Jun 2021 11:01:13 -0400 Subject: [PATCH 113/729] Updated code owners to use a team --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cea7cb05..01ea4ac9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # These are the set of folks who should review PRs on the azureRestUpdates branch. -* @markweitzel @jeffreyr @tg-msft @teglaza @mikekistler @johanste \ No newline at end of file +* @microsoft/azure-api-stewardship-board \ No newline at end of file From bc3ac8c89727912a512e2f06209208192f510e27 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 24 Jun 2021 17:22:35 -0700 Subject: [PATCH 114/729] Minor cleanup of wording and organization --- azure/Guidelines.md | 190 +++++++++++++++++++++----------------------- 1 file changed, 92 insertions(+), 98 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 541bd3b3..c56d24b6 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -53,7 +53,7 @@ Your goal is to create a developer friendly API where: :white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK w/out requiring code changes -## Azure Management Plane vs Data Plane +### Azure Management Plane vs Data Plane > Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. > A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on the conventions of the Azure ARM. @@ -88,9 +88,13 @@ Understanding how your service will be used and defining its model and interacti Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. :ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. -:ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. -:ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. + +:ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. + +:ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. + :ballot_box_with_check: **YOU SHOULD** consider doing a *code with* exercise in which you actively develop with the customer, observing and learning from their API usage. + :ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. ### Avoid surprises @@ -104,14 +108,14 @@ A major inhibitor to adoption and usage is when an API behaves in an unexpected :ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. -### Design for Resiliency -As you build out your service and API, there are a number of decisions that can be made up front that add resiliency. Addressing these as early as possible will help you iterate faster and avoid breaking changes. +### Design for Change Resiliancy +As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. :ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. :ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. -:ballot_box_with_check: **YOU SHOULD** use wider data types (e.g. 64-bit vs. 32-bit) as they are more future-proof. For example, JavaScript can only support numbers up to 253, so relying on the full width of a 64-bit number should be avoided. +:ballot_box_with_check: **YOU SHOULD** use wider data types (e.g. 64-bit vs. 32-bit) as they are more future-proof. For example, JavaScript can only support integers up to 253, so relying on the full width of a 64-bit integer should be avoided. ## Building Blocks: HTTP, REST, & JSON The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet, namely HTTP, REST, and JSON. This section will provide you with a general understanding of how these technologies should be applied when creating your service. @@ -123,14 +127,10 @@ Azure services will adhere to the HTTP specification, [RFC7231](https://tools.ie * Headers * Bodies -#### URLs -TODO: Update this section +### URLs A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. These are so central to the developer experience that careful consideration should be given when devising your URL structure. -## URLs -A Uniform Resource Locator (URL) is how developers access your service's resources. The structure of the URL is critical as it describes the service's cognitive model. - :white_check_mark: **DO** expose their service to developers via the following URL pattern: ```text https://.///// @@ -150,35 +150,42 @@ Where: * **service-root**: service-specific path (ex: blobcontainer, myqueue) +* **resource-collection**: the name of the collection, unabbreviated, pluralized + +* **resource-id**: the value of the unique id property. This MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. + :white_check_mark: **DO** treat URLs as case-sensitive (except for scheme/host). If case doesn't match what you expect, the request __MUST__ fail with the appropriate HTTP return code. > Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a GUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' -:white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a response JSON body +:white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. -:white_check_mark: **DO** return '414-URI Too Long' if a URL exceeds 2083 characters +:ballot_box_with_check: **YOU SHOULD** limit your URL's characters to `0-9 A-Z a-z - . _ ~` -:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) +:heavy_check_mark: **YOU MAY** use these other characters in the URL but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` -### Direct endpoint URLs +:ballot_box_with_check: **YOU SHOULD** use case-sensitive comparison for **resource_id** -:ballot_box_with_check: **YOU SHOULD** use case-sensitive comparison for +:heavy_check_mark: **YOU MAY** use case-insensitive comparison for a **resrouce-id** that is a GUID value -:heavy_check_mark: **YOU MAY** use case-insensitive comparison for a that is a GUID value +:white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a response JSON body ---- -A direct endpoint URL may also be used for performance/routing: +:white_check_mark: **DO** return '414-URI Too Long' if a URL exceeds 2083 characters - https://-../... +:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) - Examples: - - Request URL: https://blobstore.azure.net/contoso.com/account1/container1/blob2 - - Response ```content-location``` [RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4): https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2 - - GUID format: https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2 ---- +#### Direct endpoint URLs -:white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a GUID for or always a single verified domain. +:heavy_check_mark: **YOU MAY** support a direct endpoint URL for performance/routing: +```text +https://-../... +``` -:white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. +Examples: + - Request URL: `https://blobstore.azure.net/contoso.com/account1/container1/blob2` + - Response header ([RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4)): `content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2` + - GUID format: `https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2` + +:white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a GUID for or always a single verified domain. :heavy_check_mark: **YOU MAY** use URLs as values ``` @@ -186,16 +193,14 @@ https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy ``` ### HTTP Request / Response Pattern -The HTTP Request / Response pattern will dictate much of how your API behaves, for example; POST methods must be idempotent, GET methods may be cached, the If-Modified and etag headers determine your optimistic concurrency strategy. The URL of a service, along with its request / response, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you will make. For each request / response, the service: - -:ballot_box_with_check: **YOU SHOULD** try to limit your URL's characters to ```0-9 A-Z a-z - . _ ~``` - -:heavy_check_mark: **YOU MAY** use these other characters but they will likely require %-encoding: ```/ ? # [ ] @ ! $ & ' ( ) * + , ; =``` +The HTTP Request / Response pattern will dictate much of how your API behaves, for example; POST methods must be idempotent, GET methods may be cached, the If-Modified and etag headers determine your optimistic concurrency strategy. The URL of a service, along with its request / response, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you will make. -### HTTP Methods & Idempotency Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, all service operations (including POST) must be idempotent. +Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. --- +> Idempotent = Retrying a request has the same intended effect, even if the original request succeeded, though the response might differ + > Exactly Once Behavior = Client Retries & Service Idempotency --- @@ -207,24 +212,49 @@ Cloud applications embrace failure. Therefore, to enable customers to write faul Method | Description | Response Status Code ----|----|---- +GET | Read (i.e. list) a resource collection | 200-OK GET | Read the resource | 200-OK DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created PUT | Create/Replace the *whole* resource | 200-OK, 201-Created -TODO: To get a collection's resources (GET; see the collection section) +:white_check_mark: **DO** return the state of the resource after a PUT, PATCH, or GET operation with a ```200-OK``` or ```201-Created```. + +:white_check_mark: **DO** return a ```204-No Content``` without a resource for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) **YOU MAY** support caching and optimistic concurrency by returning resources with an etag response header and by supporting the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers. ### HTTP Query Parameters and Header Values + +:white_check_mark: **DO** validate all query parameter and request header values and return an error response if any value fails validation. + +Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. + +:white_check_mark: **DO** use the following table when translating strings: + +Data type | Document that string must be +-------- | ------- +Boolean | true / false (all lowercase) +Integer | -253+1 to +253-1 (for consistency with JSON limits on integers [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259)) +Float | [IEEE-754 binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) +String | (Un)quoted?, max length, legal characters, case-sensitive, multiple delimiter +UUID | {}? casing? hyphens? [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) +Date/Time (Header) | [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123) +Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) +Byte array | Base-64 encoded, max length + +TODO: Expand the explanation for numbers. + +TODO: Fix the links. + The table below lists the headers most used by Azure services: Header Key | Applies to | Example ------------ | ------------- | ------------- *authorization* | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) *x-ms-useragent* | Request | [see Telemetry](http://TODO:link-goes-here) -traceparent | Request | [see Distributed Tracing]](http://TODO:link-goes-here) -tracecontext | Request | [see Distributed Tracing](http://TODO:link-goes-here)++ +traceparent | Request | [see Distributed Tracing](http://TODO:link-goes-here) +tracecontext | Request | [see Distributed Tracing](http://TODO:link-goes-here) accept | Request | application/json if-match | Request | "67ab43" or * (no quotes) (see Conditional Access) if-none-match | Request | "67ab43" or * (no quotes) [see Conditional Access](http://TODO:link-goes-here) @@ -245,44 +275,11 @@ retry-after | Response | 180 [see Throttling Client Requests] :white_check_mark: **DO** compare request header values using case-sensitivity. Some exceptions exist: user-agent?, accept?, content-type?, RFC1123 dates, guids?. -:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. In addition, each request / response header. - -:no_entry: **DO NOT** use "x-" prefix for headers, unless the header already exists in production. - -#### HTTP Methods & Idempotency -Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. - -:warning: **YOU SHOULD NOT** use the POST method unless you can guarantee it can be implemented idempotently. -:white_check_mark: **DO** validate all query parameter and request header values. TODO: What to return on failure - -:white_check_mark: **DO** return the state of the resource after a PUT, PATCH, or GET operation with a ```200-OK``` or ```201-Created```. - -Method | Description | Response Status Code -----|----|---- -GET | Read the resource | 200-OK -DELETE | Remove the resource | 204-No Content; avoid 404-Not Found -PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created -PUT | Create/Replace the *whole* resource | 200-OK, 201-Created -:white_check_mark: **DO** return a ```204-No Content``` without a resource for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) - -Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. - -:white_check_mark: **DO** use the following table when translating strings: +:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. -Data type | Document that string must be --------- | ------- -Boolean | true / false (all lowercase) -Integer | -253+1 to +253-1 (limit due to IEEE-754 [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259)) -Float | [IEEE-754 binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) -String | (Un)quoted?, max length, legal characters, case-sensitive, multiple delimiter -UUID | {}? casing? hyphens? [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) -Date/Time (Header) | [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123) -Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) -Byte array | Base-64 encoded, max length - -TODO: Expand the explanation for numbers. +:no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated. -TODO: Fix the links. +:no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. #### Additional References * [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) @@ -299,19 +296,18 @@ When designing your service, it is important to optimize for the developer using :white_check_mark: **DO** ensure your resource paths make sense -:white_check_mark: **DO** simplify call with few required query parameters & JSON fields +:white_check_mark: **DO** simplify operations with few required query parameters & JSON fields :white_check_mark: **DO** establish clear contracts for string values :white_check_mark: **DO** use proper response codes/payloads so customer can self-fix #### JSON Resource Schema & Field Mutability -For a given URL path, the JSON schema (data type) should be the same for PATCH, PUT, GET, DELETE, and GETting collection items. This allows one SDK type for input/output operations and enables the response to be passed back in request. While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. -:white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH request/response, GET response, and POST response. This allows one SDK type for input/output operations and enables the response to be passed back in request. +:white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH response, GET response, and POST response on a given URL path. The PATCH request schema should contain all the same fields with no required fields. +This allows one SDK type for input/output operations and enables the response to be passed back in request. > NOTE: A service is not allowed to introduce new required fields or remove any required fields in newer versions of the service. -For PATCH, there must be a similar JSON schema with no required fields nullable. This is also not really a rest thing; more of a service implementation thing While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. REMOVE?: Tools like ADL do allow annotation of mutability, enabling more sophisticated code generation of client libraries. @@ -338,8 +334,6 @@ The following are general guidelines when using REST: :white_check_mark: **DO** use DELETE to remove a resource. -TODO: If we keep this NOTE, then it is about IDs, not about DELETE: NOTE: Ids are "Customer Content" & Azure allows their use. - :white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. :white_check_mark: **DO** treat JSON field names with case-sensitivity. @@ -376,7 +370,7 @@ PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity PUT | Overwrite resource entirely using Create/Update fields | 200-OK #### Handling Errors -When your service encounters an error, you will not be able to return the payload that was sent as part of the operation. Because you cannot put a resource in the response, you will instead use a specific header, ```x-ms-error-code``` along with a string code. In addition, the message body will have the descriptive text of the error. This error message should give enough information to the customer so they can self-diagnose the problem. It is preferrable to include additional information as part the 'inner-error'. Informative error codes and messages increase the ability for customers to be successful and lowers the overall support costs for your service. The code value that is passed in the header is also repeated as the ```code``` value in the inner-error. It is possible that clients can recover from errors gracefully at runtime. Often, the mechanism employed will be to inspect the header value and implement appropriate coping logic. Because of this, the error code, is considered part of your API contract. Example: +When your service encounters an error, you will not be able to return the payload that was sent as part of the operation. Because you cannot put a resource in the response, you will instead use a specific header, ```x-ms-error-code``` along with a string code. In addition, the message body will have the descriptive text of the error. This error message should give enough information to the customer so they can self-diagnose the problem. It is preferrable to include additional information as part the 'inner-error'. Informative error codes and messages increase the ability for customers to be successful and lowers the overall support costs for your service. The code value that is passed in the header is also repeated as the ```code``` value in the error. It is possible that clients can recover from errors gracefully at runtime. Often, the mechanism employed will be to inspect the header value and implement appropriate coping logic. Because of this, the error code, is considered part of your API contract. Example: **HEADER** @@ -399,7 +393,7 @@ When your service encounters an error, you will not be able to return the payloa :white_check_mark: **DO** Return x-ms-error-code header with string -:white_check_mark: **DO** return an ```error``` as part of the response body. The 1st ```code``` must match the ```x-ms-error-code```. +:white_check_mark: **DO** return an ```error``` as part of the response body. The `code` field of the `error` object must match the ```x-ms-error-code```. :white_check_mark: **DO** document runtime errors that are recoverable. @@ -421,7 +415,9 @@ Services, and the clients that access them, may be written in multiple languages When using strings, you must establish, and adhere to, a well defined contract for the format. For example, you should be cognizant of attributes like maximum length, legal characters, case-sensitivity, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. :white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable." + :white_check_mark: **DO** use [RFC3339] for date/time. + :white_check_mark: **DO** use [RFC4122] for UUIDs. ##### Composite types @@ -431,13 +427,14 @@ JSON also supports composing strings into higher order constructs, for example: :warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with updates when using ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. -:ballot_box_with_check: **YOU SHOULD** use maps instead of arrays. +:ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays. #### Enums & SDKs (Client libraries) -It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI specification as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation. However, your services will have client libraries in many different programming languages. And because enumerations are handled differently depending on the language, this can lead to significant interoperability issues. +It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI definition as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation. -To address these issues, Microsoft's tooling uses the concept of an "extensible enum," which effectively treats all enumerations as strings. In addition, "extensible enums" indicate to client libraries that the list of values is only a *partial* list. This enables the set of values to grow over time while ensuring stability in client libraries. +However, it is not uncommon for the set of values to grow over the life of a service. For this reason, Microsoft's tooling uses the concept of an "extensible enum," which indicates that the set of values should be treats as only a *partial* list. +This indicates to client libraries that values of the enumeration field should be effectively treated as strings. This enables the set of values to grow over time while ensuring stability in client libraries. :white_check_mark: **DO** use "extensible enums" @@ -447,8 +444,8 @@ To address these issues, Microsoft's tooling uses the concept of an "extensible :no_entry: **DO NOT** remove values from your enumeration list. This will likely result in a breaking change to client libraries & customers. -#### Discriminate polymorphic types -While polymorphism is a powerful concept in programming languages, returing "polymorphic JSON" as part of an API introduces significant complexity for implementors of client libraries and developers, especially as new versions of your service are introduced. For example, consider a service where V1 introduces two shapes, Retangles and Circles. They could be represented in JSON as follows: +#### Polymorphic types +While polymorphism is a powerful concept in programming languages, returing "polymorphic JSON" as part of an API introduces significant complexity for developers of client libraries and applications, especially as new versions of your service are introduced. For example, consider a service where V1 introduces two shapes, Retangles and Circles. They could be represented in JSON as follows: **Rectangle** ```json @@ -464,11 +461,11 @@ While polymorphism is a powerful concept in programming languages, returing "pol "subscription": { "expiration": "2024", "kind": "paid", "invoice": "123456"}}`` ``` -The first issue is that developers writing code against this JSON string contract will have a very difficult time, especially in typed languages. It will be impossible to determine what the actual type is during development, minimizing the effectiveness of tooling. At runtime, developers will have to parse the JSON, interpret the "kind" value, and *then* cast to the proper sub-class. +The first issue is that developers writing code against this JSON contract will have a very difficult time, especially in typed languages. It will be impossible to determine what the actual type is during development, minimizing the effectiveness of tooling. At runtime, developers will have to parse the JSON, interpret the "kind" value, and *then* cast to the proper sub-class. Overall, this is a very brittle design that leads to a poor developer experience, especially over time. Consider the scenario when a new shape is introduced in V2 of the API. Existing client libraries that work with V1 will have no concept of this new shape and, when receiving an unknown shape, fail. -:warning: **YOU SHOULD NOT** use polymorphic types. Instead, return discriminate types. +:warning: **YOU SHOULD NOT** use polymorphic types. Instead, return concrete types. ## Common API Patterns @@ -701,7 +698,7 @@ Azure services need to change over time. However, when changing a service, there :ballot_box_with_check: **DO** review any API changes with the Azure API Stewardship Board -:white_check_mark: **DO** use an 'api-version' query parameter with a date +:white_check_mark: **DO** use an 'api-version' query parameter with a date value ```text PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 ``` @@ -728,7 +725,7 @@ PUT https://service.azure.com/users/Jeff?api-version=2021-06-04-preview #### Use extensible enums -While removing a value from an enum is a breaking change, adding an enum can be handled with an _extensible enum_. An extensible enum is a string value that has been marked with a special marker - setting `modelAsString` to true within an `x-ms-enum` block. For example: +While removing a value from an enum is a breaking change, adding value to an enum can be handled with an _extensible enum_. An extensible enum is a string value that has been marked with a special marker - setting `modelAsString` to true within an `x-ms-enum` block. For example: ```json "createdByType": { @@ -820,24 +817,21 @@ The Microsoft REST API guidelines for Long Running Operations are an updated, cl :white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. -### Distributed Tracing & Service Telemetry -* Distributed Tracing -* Service Telemetry -* How to collect client side telemetry - - ### Bring your own storage When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS will provide the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders, nor do they have extensions. :white_check_mark: **DO** use Azure Bring Your Own Storage. + :no_entry: **DO NOT** require a fresh container per operation + :white_check_mark: **DO** use a blob prefix instead + #### Authentication How you secure and protect the data and files that your service uses will not only affect how consumable your API is, but also, how quickly you can evolve and adapt it. Implementing Role Based Access Control [RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) is the recommended approach. It is important to recognize that any roles defined in RBAC essentially become part of your API contract. For example, changing a role's permissions, e.g. restricting access, could effectively cause existing clients to break, as they may no longer have access to necessary resources. -:white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions +:white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions. -:white_check_mark: **DO** Ensure that RBAC roles MUST are backward compatible, and specifically, you cannot take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. +:white_check_mark: **DO** Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. ##### Handlilng 'downstream' errors It is not uncommon to rely on other services, e.g. storage, when implementing your service. Inevitably, the services you depend on will fail. In these situations, you can include the downstream erorr code and text in the inner-error of the response body. This provides a consistent pattern for handling errors in the services you depend upon. @@ -863,7 +857,7 @@ Depending on your requirements, there are scenarios where users of your service :white_check_mark: **DO** Enable the customer to provide an ETag to specify a specific version of a file. ##### File collections -When your users need to work with multiple files, for example a document translation service, it will be important to provide them access to the collection, and it's contents, in a consistent manner. Because there is no industry standard for working with with containers, these guidelines will recommend that you leverage Azure Storage. Following the guidelines above, you also want to ensure that you don't expose file system constructs, e.g. folders, and instead use storage constructs, e.g. blob prefixes. +When your users need to work with multiple files, for example a document translation service, it will be important to provide them access to the collection, and its contents, in a consistent manner. Because there is no industry standard for working with with containers, these guidelines will recommend that you leverage Azure Storage. Following the guidelines above, you also want to ensure that you don't expose file system constructs, e.g. folders, and instead use storage constructs, e.g. blob prefixes. :white_check_mark: **DO** When using a Shared Access Signature (SAS), ensure this is assigned to the container and that the permissions apply to the content as well. From 6b35b6dc9a56f8004c983b4321dbac5884335f3c Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Wed, 23 Jun 2021 15:46:53 -0400 Subject: [PATCH 115/729] First pass - conditional resources --- azure/Guidelines.md | 79 +++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index c56d24b6..1f8db479 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -895,64 +895,54 @@ For each of the "output" sections the following apply: TODO: Add the proper links for 'additional references' ) ### Conditional Requests +When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an ```ETag``` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. An ```ETag``` is always set by the service and will enable you to *conditionally* control how your service responds to requests, enabling you to provide predictable updates and more efficient access. -Avoid pessimistic strategies, e.g. last writer wins -- Prefer optimistic concurrency control. Last writer wins may be sufficient. It’s very expensive to build and scale pessimistic concurrency control (locks, leases, etc.) +:ballot_box_with_check: **YOU SHOULD** return an ```ETag``` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). +:ballot_box_with_check: **YOU SHOULD** use ```etags``` consistently across your API, i.e. if you use an ```ETag```, accept it on all other operations. -SHOULD: Conditional read (cache validation) -- Conditional GETs improves performance by allowing cache validation +> You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). +#### Cache control +One of the more common uses for ```ETag``` headers is cache control, also referred to a "conditional GET." This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). That is, using the value of the ```ETag``` , the server can determine if the resource has changed. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. +Implementing this strategy is relatively straightforward. First, you will return an ```ETag``` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your ```ETag```. In these scenarios, when a request is made by the client an ```ETag``` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The ```ETag``` value can then be sent in subsequent requests as part of the ```if-none-match``` header. This tells the service to compare the ```ETag``` that came in with the request, with the latest value that it has calcuted. If the two values are the same, then it is not necesary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated ```ETag``` value in the header. + +:ballot_box_with_check: **YOU SHOULD** implement conditional read strategies -Conditionaly updates (You SHOULD use optimistic concurrency) -- Conditional PUT/POST/DELETE/PATCH allow optimistic concurrency -This is a strong recommendation. -Services SHOULD force conditional updates by providing a 428 return value -(Review w/team) - -#### Optimistic concurrency -SHOULD Always return an ETag with any operation returning the resource or part of a resource (this includes getting, updating that returns a resource, listing that returns partial resources, etc.) or any update of the resource (whether the resource is returned or not). -Always return an etag. -If you return an etag, you must always accept an etag on all other operations. - - -#### Computing ETags -Strategy of how you compute the etag depends on the semantic of the etag. -Resources that are inherently versioned, should have the version. Otherwise hash. - - -etag - hash of the value -SHOULD optionally use a hash of the resource, but you must hash the entire resource and this can be expensive to compute. Especially for for listing operations. Apache uses file system info like file size + last write to generate the ETag. This can work in some cases, but make sure it's not dependent on anything specific to the server sending the response - -Versioning semantic. -MAY Best option is to add a timestamp and version identifier in your resource schema. -Timestamp shouldn't be returned with more than subsecond precision if you'll also be using the Last-Modified HTTP response header (or sub-millisecond otherwise per our general guidance).SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. - +:white_check_mark: **DO** adhere to the following table for guidance: -MAY consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes. You can also use weak etags if it's expensive to compute an ETag (i.e., weak etag is size in bytes which might not be super accurate compared to an MD5 hash of a sizeable resource). +| GET Request | Return code | Response | +|:------------|:------------|:--------------------------------------------| +| etag value = if-none-match value | 304 Not Modified | no additional information | +| etag value != if-none-match value | 200 OK | Response body include the serialized value of the resource (typically JSON) | -If you choose to use a Weak ETag, then... -(add text from mike) +> For more control over caching, please refer to the ```cache-control``` [HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). -(Review w/team, e.g. when to use in Azure) +#### Optimistic concurrency +An ```ETag``` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of ```ETags``` and the HTTP. -#### Multiple conditions: If-Match && If-Unmodified-Since && (If-None-Match || If-Modified-Since) -o If you have multiple conditions fail, return the most severe status code -see https://docs.microsoft.com/en-us/rest/api/storageservices/specifying-conditional-headers-for-blob-service-operations for examples -You MAY support preflight requests... -Preflight requests? Often supported for CORS but could be used for any potentially expensive request. Consider "EXPECT: 100-continue" for other requests that will return 100 Continue if the conditions are satisfactory or 417 Expectation Failed otherwise. Useful for conditionaly requests with large payloads. You should return the same error code that the service should use. -SHOULD provide documentation on what preflight checks will be validated. +:warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. +:white_check_mark: **DO** adhere to the following table for guidance: -- Status codes -* GET: if the comparison fails, return 304 Not Modified (consider also returning Expires/Cache-Control/Age headers for caching scenarios) -* PUT/POST/DELETE: if the comparison fails, return 412 Precondition Not Met -* Consider forcing conditional headers on resource mutation, then use 428 Precondition Required if they're not present. +| Operation | Header | Value | etag check | Return code | Response | +|:------------|:--------------|:------|:-----------|:------------|----------------| +| PATCH / PUT | if-none-match | * | check for *any* version of the resource ('*' is a wildcard used to match anything), if none are found, create the resource. | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | +| PATCH / PUT | if-none-match | * | check for *any* version of the resource, if one is found, fail the operation | 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| +| PATCH / PUT | if-match | value of etag | value of if-match equals the latest etag value on the server, confirming that the version of the resource is the most current | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | +| PATCH / PUT | if-match | value of etag | value of if-match header DOES NOT equal the latest etag value on the server, indicating a change has ocurred since after the client fetched the resource| 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| +| DELETE | if-none-match | value of etag | value does NOT match the latest value on the server | 412 Preconditioned Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request. | +| DELETE | if-none-match | value of etag | value matches the latest value on the server | 200 OK or
204 No Content | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request. | +in over time. +#### Computing ETags +The strategy that you use to compute the ```ETag``` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the ```ETag```. Another common strategy for determining the value of an ```ETag``` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. -- If-Match header - should support multiple values that are Or-ed together per HTTP/1.1. +:heavy_check_mark: **YOU MAY** use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. +:ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. +:heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. ### Distributed Tracing & Telemetry Azure SDK client guidelines specify that client libraries must send telemetry data through the ```User-Agent``` header, ```X-MS-UserAgent``` header, and Open Telemetry. Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. @@ -960,14 +950,11 @@ Client libraries are required to send telemetry and distributed tracing informat :white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. :no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. - #### Additional References * [Azure SDK client guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) * [Azure SDK User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) * [Azure SDK Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) * [Open Telemetry](https://opentelemetry.io/) - - ## Final Thoughts / Summary * Careful consideration up front * Long term decisions that are often codified in SDKs, CODE, etc. From 3c2a44c9c9e49b8e8b5ed0f68d41754d5e0476c8 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Mon, 21 Jun 2021 19:26:10 -0400 Subject: [PATCH 116/729] Fist pass - summary --- azure/Guidelines.md | 45 ++++++++++++--------------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 1f8db479..ecd2ed11 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -926,36 +926,15 @@ An ```ETag``` should also be used to reflect the create, update, and delete poli :white_check_mark: **DO** adhere to the following table for guidance: -| Operation | Header | Value | etag check | Return code | Response | -|:------------|:--------------|:------|:-----------|:------------|----------------| -| PATCH / PUT | if-none-match | * | check for *any* version of the resource ('*' is a wildcard used to match anything), if none are found, create the resource. | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | -| PATCH / PUT | if-none-match | * | check for *any* version of the resource, if one is found, fail the operation | 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| -| PATCH / PUT | if-match | value of etag | value of if-match equals the latest etag value on the server, confirming that the version of the resource is the most current | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | -| PATCH / PUT | if-match | value of etag | value of if-match header DOES NOT equal the latest etag value on the server, indicating a change has ocurred since after the client fetched the resource| 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| -| DELETE | if-none-match | value of etag | value does NOT match the latest value on the server | 412 Preconditioned Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request. | -| DELETE | if-none-match | value of etag | value matches the latest value on the server | 200 OK or
204 No Content | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request. | -in over time. -#### Computing ETags -The strategy that you use to compute the ```ETag``` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the ```ETag```. Another common strategy for determining the value of an ```ETag``` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. - -:heavy_check_mark: **YOU MAY** use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. - -:ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. - -:heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. -### Distributed Tracing & Telemetry -Azure SDK client guidelines specify that client libraries must send telemetry data through the ```User-Agent``` header, ```X-MS-UserAgent``` header, and Open Telemetry. -Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. - -:white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. - -:no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. -#### Additional References -* [Azure SDK client guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) -* [Azure SDK User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) -* [Azure SDK Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) -* [Open Telemetry](https://opentelemetry.io/) -## Final Thoughts / Summary -* Careful consideration up front -* Long term decisions that are often codified in SDKs, CODE, etc. -* Reach out and engage the stewardship team! +## Final thoughts +These guidelines describe the ..., and the common patterns that teams encounter when building an Azure service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. + +The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. +### Typical review session +When engaging with the API REST Stewardship board, your working sessions will generally focus on three areas: +* Correctness - Is +* Consistency - Your services should look and behave as though they are natural part of the Azure platform. +* Well formed - Do your services adhere to REST and Azure standards, e.g. proper return codes, use of headers. +* Sustainable - Your APIs will grow and change over time and leveraging the common patterns described in this document will help you mimize your tech debt and move fast with confidence. + +It was once said that "all roads lead to Rome." For cloud services, the equivalent might be that "all 'roads' start with your API." That could not be more true than at Microsoft, where client libraries, documentation, and many other artifacts all originate from the fundamental way you choose to expose your service. With careful consideration at the outset of your service, the architectural stewardship of the API boar, and the thoughtful application of these guidelines, you will be able to produce a consistent, well formed API that will delight our customers. \ No newline at end of file From bf4a08738acfb487a78425600180d95bc7d55a99 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Mon, 21 Jun 2021 19:35:15 -0400 Subject: [PATCH 117/729] oops. fixed a bullet in the summary --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ecd2ed11..48d7cde2 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -932,7 +932,7 @@ These guidelines describe the ..., and the common patterns that teams encounter The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. ### Typical review session When engaging with the API REST Stewardship board, your working sessions will generally focus on three areas: -* Correctness - Is +* Correctness - Your service should leverage the proper HTTP verbs, return codes, and respect the core constructs of a REST API, e.g. idempotency, that are standard throughout the industry. * Consistency - Your services should look and behave as though they are natural part of the Azure platform. * Well formed - Do your services adhere to REST and Azure standards, e.g. proper return codes, use of headers. * Sustainable - Your APIs will grow and change over time and leveraging the common patterns described in this document will help you mimize your tech debt and move fast with confidence. From fccbdcd07e32caa662aa23fb68ff90e35ab69774 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 25 Jun 2021 10:36:27 -0400 Subject: [PATCH 118/729] Updated based on review comments. --- azure/Guidelines.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 48d7cde2..66addc93 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -927,14 +927,14 @@ An ```ETag``` should also be used to reflect the create, update, and delete poli :white_check_mark: **DO** adhere to the following table for guidance: ## Final thoughts -These guidelines describe the ..., and the common patterns that teams encounter when building an Azure service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. +These guidelines describe the upfront design considerations, technology building blocks, and common patterns that Azure teams encounter when building an API for their service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. -The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. +The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. ### Typical review session When engaging with the API REST Stewardship board, your working sessions will generally focus on three areas: * Correctness - Your service should leverage the proper HTTP verbs, return codes, and respect the core constructs of a REST API, e.g. idempotency, that are standard throughout the industry. * Consistency - Your services should look and behave as though they are natural part of the Azure platform. * Well formed - Do your services adhere to REST and Azure standards, e.g. proper return codes, use of headers. -* Sustainable - Your APIs will grow and change over time and leveraging the common patterns described in this document will help you mimize your tech debt and move fast with confidence. +* Sustainable - Your APIs will grow and change over time and leveraging the common patterns described in this document will help you minimize your tech debt and move fast with confidence. -It was once said that "all roads lead to Rome." For cloud services, the equivalent might be that "all 'roads' start with your API." That could not be more true than at Microsoft, where client libraries, documentation, and many other artifacts all originate from the fundamental way you choose to expose your service. With careful consideration at the outset of your service, the architectural stewardship of the API boar, and the thoughtful application of these guidelines, you will be able to produce a consistent, well formed API that will delight our customers. \ No newline at end of file +It was once said that "all roads lead to Rome." For cloud services, the equivalent might be that "all 'roads' start with your API." That could not be more true than at Microsoft, where client libraries, documentation, and many other artifacts all originate from the fundamental way you choose to expose your service. With careful consideration at the outset of your development effort, the architectural stewardship of the API board, and the thoughtful application of these guidelines, you will be able to produce a consistent, well formed API that will delight our customers. \ No newline at end of file From 2beab97b39cbc7dd9aff6aa8fef0be25a794b7b1 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Mon, 21 Jun 2021 13:05:55 -0400 Subject: [PATCH 119/729] minor cleanups --- azure/Guidelines.md | 224 +------------------------------------------- 1 file changed, 1 insertion(+), 223 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 66addc93..97753e30 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -218,10 +218,6 @@ DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created PUT | Create/Replace the *whole* resource | 200-OK, 201-Created -:white_check_mark: **DO** return the state of the resource after a PUT, PATCH, or GET operation with a ```200-OK``` or ```201-Created```. - -:white_check_mark: **DO** return a ```204-No Content``` without a resource for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) - **YOU MAY** support caching and optimistic concurrency by returning resources with an etag response header and by supporting the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers. ### HTTP Query Parameters and Header Values @@ -471,224 +467,6 @@ Overall, this is a very brittle design that leads to a poor developer experience ### Performing an Action -### Collections - -The standard REST pattern for accessing a collection of resources is with a _GET_ method on the collection URL. -This operation is commonly called the "list operation" for the resource, since the primary content of the response -is an array (list) of the resource type. Query parameters on the list operation may be provided to control -which resources are returned and in what order. - -Resource collections can often be arbitrarily large, increasing lookup time as well as the size of the responses being sent over the wire. Therefore, it is important that list operations implement pagination. - -:white_check_mark: **DO** provide a list operation for each resource type. - -:white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field to contain the collection of resources. - -:ballot_box_with_check: **YOU SHOULD** at a minimum include the id field in each resource. Also include the _ETag_ if supported for this resource type. - -:ballot_box_with_check: **YOU SHOULD** use _value_ as the name of the top-level array field -- there are some allowable exceptions to this guidance. - -:white_check_mark: **DO** implement pagination of list operation responses unless there is no possibility of a collection exceeding a size appropriate for a single response. -In particular, include a top-level field in the response named _nextLink_ that contains an opaque absolute URL to return the next page of results when there are additional items in the collection. - -:no_entry: **DO NOT** include a _nextLink_ field in the response if there are no additional items in the collection. - - -:white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this. - -Example: -```json -{ - "value":[ - { "id": "Item 1","price": 99.95,"sizes": null}, - { … }, - { … }, - { "id": "Item 99","price": 59.99,"sizes": null} - ], - "nextLink": "{opaqueUrl}" -} -``` - -#### Query options - -A number of query parameters that may be supported on a list operation to control which resources are returned and in what order. A summary of these query parameters is shown in the following table, with more detail given below. All these query parameters are optional. - -| Parameter name | type | description | -| -------------- | ---- | ----------- | -| _filter_ | string | an expression on the resource type that selects the resources to be returned | -| _orderby_ | array of string | a list of expressions that specify the order of the returned resources | -| _skip_ | integer | an offset into the collection of the first resource to be returned | -| _top_ | integer | the maximum number of resources to return from the collection | -| _maxpagesize_ | integer | the maximum number of resources to include in a single response | - -:white_check_mark: **DO** treat these query parameter names as case-sensitve. - -:no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). - -:white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. - -:white_check_mark: **DO** apply _select_ or _expand_ options after applying all the query options in the table above. - -#### filter - -:heavy_check_mark: **YOU MAY** support filtering of the results of a list operation with the _filter_ query parameter. - -The value of the _filter_ option is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response. - -:white_check_mark: **DO** omit all resources from the collection for which the _filter_ expression evaluates to false or to null, or references properties that are unavailable due to permissions. - -Example: return all Products whose Price is less than $10.00 - -```http -GET https://api.contoso.com/products?filter=price lt 10.00 -``` - -##### filter operators - -:ballot_box_with_check: **YOU SHOULD** support the following minimal set of operators in _filter_ expressions. - -Operator | Description | Example --------------------- | --------------------- | ----------------------------------------------------- -Comparison Operators | | -eq | Equal | city eq 'Redmond' -ne | Not equal | city ne 'London' -gt | Greater than | price gt 20 -ge | Greater than or equal | price ge 10 -lt | Less than | price lt 20 -le | Less than or equal | price le 100 -Logical Operators | | -and | Logical and | price le 200 and price gt 3.5 -or | Logical or | price le 3.5 or price gt 200 -not | Logical negation | not price le 3.5 -Grouping Operators | | -( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 - -:white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if a client includes an operator in a _filter_ expression that is not supported by the operation. - -:white_check_mark: **DO** use the following operator precedence for supported operators when evaluating _filter_ expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence: - -| Group | Operator | Description | -|:----------------|:---------|:----------------------| -| Grouping | ( ) | Precedence grouping | -| Unary | not | Logical Negation | -| Relational | gt | Greater Than | -| | ge | Greater than or Equal | -| | lt | Less Than | -| | le | Less than or Equal | -| Equality | eq | Equal | -| | ne | Not Equal | -| Conditional AND | and | Logical And | -| Conditional OR | or | Logical Or | - -##### Operator examples -The following examples illustrate the use and semantics of each of the logical operators. - -Example: all products with a name equal to 'Milk' - -```http -GET https://api.contoso.com/products?filter=name eq 'Milk' -``` - -Example: all products with a name not equal to 'Milk' - -```http -GET https://api.contoso.com/products?filter=name ne 'Milk' -``` - -Example: all products with the name 'Milk' that also have a price less than 2.55: - -```http -GET https://api.contoso.com/products?filter=name eq 'Milk' and price lt 2.55 -``` - -Example: all products that either have the name 'Milk' or have a price less than 2.55: - -```http -GET https://api.contoso.com/products?filter=name eq 'Milk' or price lt 2.55 -``` - -Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: - -```http -GET https://api.contoso.com/products?filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 -``` - -#### orderby - -:heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the _orderby_ query parameter. - -The value of the _orderby_ parameter is a comma-separated list of expressions used to sort the items. -A special case of such an expression is a property path terminating on a primitive property. - -Each expression in the _orderby_ parameter value may include the suffix "asc" for ascending or "desc" for descending, separated from the expression by one or more spaces. - -:white_check_mark: **DO** sort the collection in ascending order on an expression if "asc" or "desc" is not specified. - -:white_check_mark: **DO** sort NULL values as "less than" non-NULL values. - -:white_check_mark: **DO** sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on. - -:white_check_mark: **DO** use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically. - -:white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if the client requests sorting by a field that is not supported by the operation. - -For example, to return all people sorted by name in ascending order: - -```http -GET https://api.contoso.com/people?$orderBy=name -``` - - -For example, to return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. - -```http -GET https://api.contoso.com/people?$orderBy=name desc,hireDate -``` - -Sorting MUST compose with filtering such that: - -```http -GET https://api.contoso.com/people?filter=name eq 'david'&orderby=hireDate -``` - -will return all people whose name is David sorted in ascending order by hireDate. - -##### Considerations for sorting with pagination - -:white_check_mark: **DO** use the same filtering options and sort order for all pages of a paginated list operation response. - -##### skip - -:heavy_check_mark: **YOU MAY** allow clients to pass the _skip_ query parameter to specify an offset into collection of the first resource to be returned. - -:white_check_mark: **DO** define the _skip_ parameter as an integer with a default and minimum value of 0. - -##### top - -:heavy_check_mark: **YOU MAY** allow clients to pass the _top_ query parameter to specify the maximum number of resources to return from the collection. - -:white_check_mark: **DO** define the _top_ parameter as an integer with a minimum value of 1. - -:white_check_mark: **DO** return all resources of the collection, starting from _skip_ if specified, possibly paginated by server-driven pagination, if _top_ is not specified. - -:white_check_mark: **DO** return the _top_ resources, accumulated over all server-driven paging, if the collection contains at least _top_ number of resources. Returning fewer than _top_ resources can be interpreted by the client to mean that there are no more resources in the collection. - -##### maxpagesize - -:heavy_check_mark: **YOU MAY** allow clients to pass the _maxpagesize_ query parameter to specify the maximum number of resources to include in a single response. - -:white_check_mark: **DO** define the _maxpagesize_ parameter as an optional integer with a default value appropriate for the collection. - -:white_check_mark: **DO** make clear in documentation of the _maxpagesize_ parameter that the operation may choose to return fewer resources than the value specified in a single response. - -:white_check_mark: **DO** apply any _skip_ value specified by the client before applying the _maxpagesize_ to the collection. - -:no_entry: **DO NOT** support both _top_ and _maxpagesize_ unless server-side paging provides stronger consistency guarantees and these are described in the documentation. - -> MDK: Make sure that the guidance below is captured elsewhere then remove from here. -> Note: If the server can't honor _skip_ or _maxpagesize_ (e.g. a negative value is specified), the server MUST return an error to the client informing about it instead of just ignoring the query options. -> This will avoid the risk of the client making assumptions about the data returned. - ### API Versioning Azure services need to change over time. However, when changing a service, there are 2 requirements: @@ -744,7 +522,7 @@ While removing a value from an enum is a breaking change, adding value to an enu } ``` -Always model an enum as a string unless you are positive that the symbol set will **NEVER** change over time. +:ballot_box_with_check: **DO** slways model an ```enum``` as a string unless you are positive that the symbol set will **NEVER** change over time. #### Version discovery From 91ce5a02c9c6f2b6fb48831aac3166fabdbd06e9 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 25 Jun 2021 06:55:32 -0700 Subject: [PATCH 120/729] Update azure/Guidelines.md Co-authored-by: Mike Kistler --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 97753e30..25f8ec8b 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -522,7 +522,7 @@ While removing a value from an enum is a breaking change, adding value to an enu } ``` -:ballot_box_with_check: **DO** slways model an ```enum``` as a string unless you are positive that the symbol set will **NEVER** change over time. +:ballot_box_with_check: **DO** model an ```enum``` as a string unless you are positive that the symbol set will **NEVER** change over time. #### Version discovery From 116a5a1506b5966bc0bbe1a397aca04e31869d94 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Mon, 21 Jun 2021 15:33:22 -0400 Subject: [PATCH 121/729] First pass at action section --- azure/Guidelines.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 25f8ec8b..e07a82ee 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -466,6 +466,23 @@ Overall, this is a very brittle design that leads to a poor developer experience ## Common API Patterns ### Performing an Action +The REST specificaiton is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services have the requirement to perform an action on a resource, e.g. getting the thumbnail of an image, sending an SMS message. To perform an action on a resource, we introduce the concept of a "controller resource," or simply "controller." +> A controller resource models a procedural concept. Controller resources are like executable functions, with parameters and return values; inputs and outputs.[REST API Tutorial](https://restfulapi.net/resource-naming/) + +One reason for using controllers is API consistency. The URL to the controller will include the resource upon which the operation will take place. Typically, the URL pattern for actions should be constructed as follows: First, specify the service and the resource collection. Next, identify the exact resource to operate on using the resource's id. Finally, use the "/:" pattern to indicate the action on the resource. Query parameters are used to send any parameters to the action. Consider the example of a service that sends a text message. The action URl would look similar to the following: +> **URL Pattern** +> +> ```https://///:?``` +> +> **SMS Example** +> +> ```https://myTextingService/users/12345/:send-sms?Text="Hello"``` + +While this is a generally recognized pattern in the industry, these Azure API guidelines add further precision to how this pattern should be modeled. + +:white_check_mark: **DO** use a POST operation for any action on a resource. Further, this operation **MUST** be idempotent. + +:ballot_box_with_check: **YOU SHOULD** use a verb to name your action. ### API Versioning From 99761169e223b6326e88a38ebd2e01e78aebe313 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 25 Jun 2021 16:27:42 -0700 Subject: [PATCH 122/729] Review pass --- azure/Guidelines.md | 634 +++++++++++++++++++++++++++++--------------- 1 file changed, 422 insertions(+), 212 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index e07a82ee..cf23c364 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,4 +1,3 @@ - > ### Background > This document is a work in progress and is intended to become an updated version of the Azure REST API guidelines. It is based on the best practices for building REST APIs, the existing Microsoft and Azure API Guidelines, and feedback from the API Stewardship Board. Your thoughts, comments, issues, pull requests, and all other forms of feedback are welcomed and encouraged. You can also reach out to Mark Weitzel as well. > @@ -16,10 +15,10 @@ These guidelines offer prescriptive guidance that Azure service teams MUST follo - Developer friendly via consistent patterns & web standards (HTTP, REST, JSON) - Efficient & cost-effective - Work well with SDKs in many programming languages -- Customers can create fault-tolerant apps by supporting retries/idempotency +- Customers can create fault-tolerant apps by supporting retries/idempotency/optimistic concurrency - Sustainable & versionable via clear API contracts with 2 requirements: - - Customer workloads must never break due to a service change - - Customers can adopt a version without requiring code changes + 1. Customer workloads must never break due to a service change + 2. Customers can adopt a version without requiring code changes Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. @@ -28,63 +27,76 @@ Technology and software is constantly changing and evolving, and as such, this i ### Prescriptive Guidance This document offers prescriptive guidance labeled as follows: -:white_check_mark: **DO** adopt this guideline or follow this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board[TODO: mail link? - not for public people] prior to implementation. +:white_check_mark: **DO** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board[TODO: mail link? - not for public people] prior to implementation. -:ballot_box_with_check: **YOU SHOULD** strongly consider this guideline. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. +:ballot_box_with_check: **YOU SHOULD** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. -:heavy_check_mark: **YOU MAY** consider this guideline if appropriate to your situation. No notification to the Azure HTTP/REST Stewardship Board is required. +:heavy_check_mark: **YOU MAY** consider this pattern if appropriate to your situation. No notification to the Azure HTTP/REST Stewardship Board is required. -:warning: **YOU SHOULD NOT** strongly consider avoiding the described pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. +:warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. -:no_entry: **DO NOT** follow this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. +:no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. *If you feel you need an exception, or need clarity based on your situation, please contact the Azure HTTP/REST Stewardship Board prior to release of your API.* ## Advice for New Services -Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, especially over time. +Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, especially over time. -This document provides Microsoft teams building Azure services with a set of guidelines that will help service teams build great APIs. The guidelines can be applied to create an API that is approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation, and enabling a broad audience of developers across multiple languages. +This document provides Microsoft teams building Azure services with a set of guidelines that help service teams build great APIs. The guidelines create APIs that are approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault-tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation enabling a broad audience of developers across multiple languages. -Azure Service teams should engage the API Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. +Azure Service teams should engage the Azure HTTP/REST Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. Your goal is to create a developer friendly API where: :white_check_mark: **DO** ensure that customer workloads never break -:white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK w/out requiring code changes +:white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library without requiring code changes ### Azure Management Plane vs Data Plane > Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. -> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Rarely, a subset of operations may be useful to both administrators and users, in which case it should appear in both APIs. Although the best practices and patterns described in this document apply to all REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on the conventions of the Azure ARM. -### Start with developer experience -A great API starts with a well thought out and designed service. It is extremely difficult to create an elegant API that will work well on top of a service that is poorly designed. It is important that your development team builds some client code using the API. Hold reviews and share what is learend with your team. Engage with your customers during a preview release. If during a preview you discover that customers are struggling to use your API, e.g. they don't understand the abstraction layer, take the time to fix your service abstractions. This will benefit the developer and your team. Put yourself in the developer's shoes and think deeply about how they will be using your API and your service. +> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful to both administrators and developers. In this case, the operation can appear in both APIs. Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on the conventions of the Azure ARM. + +### Start with the Developer Experience +A great API starts with a well thought out and designed service. Your service should define simple/understandable abstractions with each given a clear name that you use consistently throughout your API and documentation. There must also be an unambiguous relationship between these abstractions. + +It is extremely difficult to create an elegant API that works well on top of a poorly designed service; the service team and customers will live with this pain for years to come. So, the service team should empathize with customers by: + - Building apps that consume the API + - Hold reviews and share what is learned with your team + - Get customer feedback from API previews + - Thinking about the code that a customer writes both before and after an HTTP operation + - Initializing and reading from the data structures your service requires + - Thinking about which errors are recoverable at runtime as opposed to indicating a bug in the customer code that must be fixed -Think about the code that a customer will write both before and after the REST API call. What data structures will they need to assemble? What is the most likely next call? +The whole purpose of a preview to address feedback by improving abstractions, naming, relationships, API operations, and so on. It is OK to make breaking changes during a preview to improve the experience now so that it is sustainable long term. :white_check_mark: **DO** provide examples in multiple languages :white_check_mark: **DO** include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). +### Focus on Hero Scenarios +It is important to realize that writing an API is, in many cases, the easiest part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, client libraries, examples, blog posts, videos, and supporting customers in perpetuity. In fact, implementing an API is of miniscule cost compared to all the other downstream activities. + +> For this reason, it is much better to ship with fewer features and only add new features over time as required by customers. + +Focusing on hero scenarios reduces development, support, and maintenance costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A telltale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. -### Focus on hero scenarios -It is important to realize that writing an API is, in many cases, the easist part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, and creation of client libraries and examples.Focusing on hero scenarios reduces development, support, and maintenace costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A tell tale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. Service teams: +:white_check_mark: **DO** define "hero scenarios" first including abstractions, naming, relationships, and then define the API describing the operations required -:white_check_mark: **DO** define "hero scenarios" first, then the operations required, & then design the API +:white_check_mark: **DO** provide example code demonstrating the "Hero Scenarios" -:white_check_mark: **DO** provide example code that demonstrates their "Hero Scenarios." +:no_entry: **DO NOT** proactively add APIs for speculative features customers might want -:no_entry: **DO NOT** add APIs for speculative features customers might want +### Start with your API Definition +Understanding how your service is used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It reflects the abstractions & naming decisions and makes it easy for developers to implement the hero scenarios. -### Start with your API definition -Understanding how your service will be used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It should reflect the naming decisions and make it easy for developers to implement your hero scenarios. -:white_check_mark: **DO** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) that describes the service. The OpenAPI Definition is a key element of the Azure SDK plan and essential to improving the documentation, usability and discoverability of services. +:white_check_mark: **DO** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. -:ballot_box_with_check: **YOU SHOULD** describe their services using ADL *[LINK TO ADL HERE]*. +:ballot_box_with_check: **YOU SHOULD** describe the service using ADL *[LINK TO ADL HERE]*. -:ballot_box_with_check: **YOU SHOULD** use ADL to generate the required OpenAPI Definition. +:ballot_box_with_check: **YOU SHOULD** use ADL to generate the required OpenAPI definition. -### Use previews to iterate +### Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. :ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. @@ -97,7 +109,7 @@ Understanding how your service will be used and defining its model and interacti :ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. -### Avoid surprises +### Avoid Surprises A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. :ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint __SHOULD__ work with a single type to avoid problems during SDK creation. @@ -108,7 +120,7 @@ A major inhibitor to adoption and usage is when an API behaves in an unexpected :ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. -### Design for Change Resiliancy +### Design for Change Resiliency As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. :ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. @@ -118,62 +130,50 @@ As you build out your service and API, there are a number of decisions that can :ballot_box_with_check: **YOU SHOULD** use wider data types (e.g. 64-bit vs. 32-bit) as they are more future-proof. For example, JavaScript can only support integers up to 253, so relying on the full width of a 64-bit integer should be avoided. ## Building Blocks: HTTP, REST, & JSON -The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet, namely HTTP, REST, and JSON. This section will provide you with a general understanding of how these technologies should be applied when creating your service. +The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet; namely HTTP, REST, and JSON. This section provides you with a general understanding of how these technologies should be applied when creating your service. ### HTTP -Azure services will adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231), as closely possible when presenting their API. This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: +Azure services must adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231). This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: * [Uniform Resource Locators (URLs)](URLS) * HTTP Methods -* Headers +* Request & Response Headers * Bodies -### URLs +### Uniform Resource Locators (URLs) -A Uniform Resource Locator (URL) is how developers will access the resources of your service. Ultimately, URLs will be how developers begin to form a cognitive model of your service. These are so central to the developer experience that careful consideration should be given when devising your URL structure. +A Uniform Resource Locator (URL) is how developers access the resources of your service. Ultimately, URLs are how developers form a cognitive model of your service's resources. -:white_check_mark: **DO** expose their service to developers via the following URL pattern: +:white_check_mark: **DO** use this URL pattern: ```text https://.///// ``` Where: -* **service**: name of the service (ex: blobstore, servicebus, directory, or management) -* **cloud**: cloud domain name (see Azure CLI's "az cloud list") - | Cloud | Domain | - | ------------- | ----- | - | Public | azure.net | - | China | chinacloudapi.cn | - | US Government | usgovcloudapi.net | - | German | cloudap.de | - -* **tenant**: globally unique ID of container representing tenant isolation, billing, enforced quotas, lifetime of containees (ex: subscription UUID) - -* **service-root**: service-specific path (ex: blobcontainer, myqueue) - -* **resource-collection**: the name of the collection, unabbreviated, pluralized - -* **resource-id**: the value of the unique id property. This MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. - -:white_check_mark: **DO** treat URLs as case-sensitive (except for scheme/host). If case doesn't match what you expect, the request __MUST__ fail with the appropriate HTTP return code. -> Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a GUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' + | Field | Description + | - | - | + | service | Name of the service (ex: blobstore, servicebus, directory, or management) + | cloud | Cloud domain name (see Azure CLI's "az cloud list")

CloudDomain
Publicazure.net
US Governmentusgovcloudapi.net
Chinachinacloudapi.cn
Germancloudapi.de
+ | tenant | Globally-unique ID of container representing tenant isolation, billing, enforced quotas, lifetime of containees (ex: subscription UUID) + | service‑root | Service-specific path (ex: blobcontainer, myqueue) + | resource‑collection | Name of the collection, unabbreviated, pluralized + | resource‑id | Value of the unique id property. This MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. :white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. +:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) + :ballot_box_with_check: **YOU SHOULD** limit your URL's characters to `0-9 A-Z a-z - . _ ~` :heavy_check_mark: **YOU MAY** use these other characters in the URL but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` -:ballot_box_with_check: **YOU SHOULD** use case-sensitive comparison for **resource_id** +:white_check_mark: **DO** return '414-URI Too Long' if a URL exceeds 2083 characters -:heavy_check_mark: **YOU MAY** use case-insensitive comparison for a **resrouce-id** that is a GUID value +:white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request __MUST__ fail with the appropriate HTTP return code. +> Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' :white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a response JSON body -:white_check_mark: **DO** return '414-URI Too Long' if a URL exceeds 2083 characters - -:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) - -#### Direct endpoint URLs +#### Direct Endpoint URLs :heavy_check_mark: **YOU MAY** support a direct endpoint URL for performance/routing: ```text @@ -185,7 +185,7 @@ Examples: - Response header ([RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4)): `content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2` - GUID format: `https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2` -:white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a GUID for or always a single verified domain. +:white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for or always a single verified domain. :heavy_check_mark: **YOU MAY** use URLs as values ``` @@ -193,49 +193,54 @@ https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy ``` ### HTTP Request / Response Pattern -The HTTP Request / Response pattern will dictate much of how your API behaves, for example; POST methods must be idempotent, GET methods may be cached, the If-Modified and etag headers determine your optimistic concurrency strategy. The URL of a service, along with its request / response, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you will make. +The HTTP Request / Response pattern dictates how your API behaves. For example: POST methods that create resources must be idempotent, GET method results may be cached, the If-Modified and etag headers offer optimistic concurrency. The URL of a service, along with its request/response bodies, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. -Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, all service operations (including POST) must be idempotent. -Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. +Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, all service operations (including POST) must be idempotent. Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. --- -> Idempotent = Retrying a request has the same intended effect, even if the original request succeeded, though the response might differ - > Exactly Once Behavior = Client Retries & Service Idempotency --- -:white_check_mark: **DO** ensure that ALL HTTP methods are idempotent. +:white_check_mark: **DO** ensure that __all__ HTTP methods are idempotent. + +:ballot_box_with_check: **YOU SHOULD** use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent. -:warning: **YOU SHOULD NOT** use POST method unless you implement idempotently via an Idempotent-token header (TODO: fix this up). TODO: Say how POST returns the URL of the create resource with 201 +:heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response __must__ return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (TODO: See link) -:white_check_mark: **DO** adhere to the return codes in the following table when implementing your API: +:white_check_mark: **DO** adhere to the return codes in the following table when the method is successful: Method | Description | Response Status Code -----|----|---- -GET | Read (i.e. list) a resource collection | 200-OK -GET | Read the resource | 200-OK +-------|-------------|--------------------- +PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created +PUT | Create/Replace the *whole* resource | 200-OK, 201-Created +POST | Create new resource (ID set by service) | 201-Created with URL of created resource +GET | Read (i.e. list) a resource collection | 200-OK +GET | Read the resource | 200-OK DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found -PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created -PUT | Create/Replace the *whole* resource | 200-OK, 201-Created -**YOU MAY** support caching and optimistic concurrency by returning resources with an etag response header and by supporting the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers. +:white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase -### HTTP Query Parameters and Header Values +:white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a ```200-OK``` or ```201-Created```. + +:white_check_mark: **DO** return a ```204-No Content``` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) -:white_check_mark: **DO** validate all query parameter and request header values and return an error response if any value fails validation. +:white_check_mark: **DO** support caching and optimistic concurrency by honoring the the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers and by returning the etag and last-modified response headers +### HTTP Query Parameters and Header Values Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. +:white_check_mark: **DO** validate all query parameter and request header values and return an error response if any value fails validation. + :white_check_mark: **DO** use the following table when translating strings: Data type | Document that string must be --------- | ------- -Boolean | true / false (all lowercase) -Integer | -253+1 to +253-1 (for consistency with JSON limits on integers [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259)) -Float | [IEEE-754 binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) -String | (Un)quoted?, max length, legal characters, case-sensitive, multiple delimiter -UUID | {}? casing? hyphens? [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) -Date/Time (Header) | [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123) +--------- | ------- +Boolean | true / false (all lowercase) +Integer | -253+1 to +253-1 (for consistency with JSON limits on integers [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259)) +Float | [IEEE-754 binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) +String | (Un)quoted?, max length, legal characters, case-sensitive, multiple delimiter +UUID | 123e4567-e89b-12d3-a456-426614174000 (no {}s, hyphens, case-insensitive) [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) +Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) Byte array | Base-64 encoded, max length @@ -245,35 +250,35 @@ Byte array | Base-64 encoded, max length The table below lists the headers most used by Azure services: -Header Key | Applies to | Example ------------- | ------------- | ------------- -*authorization* | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) -*x-ms-useragent* | Request | [see Telemetry](http://TODO:link-goes-here) -traceparent | Request | [see Distributed Tracing](http://TODO:link-goes-here) -tracecontext | Request | [see Distributed Tracing](http://TODO:link-goes-here) -accept | Request | application/json -if-match | Request | "67ab43" or * (no quotes) (see Conditional Access) -if-none-match | Request | "67ab43" or * (no quotes) [see Conditional Access](http://TODO:link-goes-here) -If-Modified-Since | Request | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) -If-Unmodified-Since | Request | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) +Header Key | Applies to | Example +------------------- | ---------- | ------------- +*authorization* | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) +*x‑ms‑useragent* | Request | [see Telemetry](http://TODO:link-goes-here) +traceparent | Request | [see Distributed Tracing](http://TODO:link-goes-here) +tracecontext | Request | [see Distributed Tracing](http://TODO:link-goes-here) +accept | Request | application/json +if-match | Request | "67ab43" or * (no quotes) (see Conditional Access) +if-none-match | Request | "67ab43" or * (no quotes) [see Conditional Access](http://TODO:link-goes-here) +If-Modified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) [see Optimistic Concurrency](http://TODO:link-goes-here) +If‑Unmodified‑Since | Request | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) date [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123) | Both | Sun, 06 Nov 1994 08:49:37 GMT -*content-type* | Both | application/merge-patch+json -*content-length* | Both | 1024 -*x-ms-request-id* | Response | [see Customer Support](http://TODO:link-goes-here) -etag | Response | "67ab43" [see Conditional Access](http://TODO:link-goes-here) -retry-after | Response | 180 [see Throttling Client Requests] -*x-ms-error-code* | Response | [see Processing a REST Request](http://TODO:link-goes-here) -*Last-Modified* | Response | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) +*content-type* | Both | application/merge-patch+json +*content-length* | Both | 1024 +*x-ms-request-id* | Response | [see Customer Support](http://TODO:link-goes-here) +etag | Response | "67ab43" [see Conditional Access](http://TODO:link-goes-here) +last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) +*x-ms-error-code* | Response | [see Processing a REST Request](http://TODO:link-goes-here) +retry-after | Response | 180 [see Throttling Client Requests] -:white_check_mark: **DO** specify headers using kebab-casing. +:white_check_mark: **DO** support all headers shown in *italics* -:white_check_mark: **DO** compare request header names using case-insensitivity +:white_check_mark: **DO** specify headers using kebab-casing -:white_check_mark: **DO** compare request header values using case-sensitivity. Some exceptions exist: user-agent?, accept?, content-type?, RFC1123 dates, guids?. +:white_check_mark: **DO** compare request header names using case-insensitivity -:ballot_box_with_check: **YOU SHOULD** properly handle all headers annotated in *italics*. +:white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it -:no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated. +:no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated :no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. @@ -282,8 +287,8 @@ retry-after | Response | 180 [see Throttling Client Requests] * [Standard HTTP Headers](https://httpwg.org/specs/rfc7231.html#header.field.registration) * [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) -### REST -REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you will define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to retrieve and update the state of your resource. Note, it's important to model resource state, not behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. +### REpresentational State Transfer (REST) +REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. TODO: Add link to behavior section When designing your service, it is important to optimize for the developer using your API. @@ -296,32 +301,28 @@ When designing your service, it is important to optimize for the developer using :white_check_mark: **DO** establish clear contracts for string values -:white_check_mark: **DO** use proper response codes/payloads so customer can self-fix +:white_check_mark: **DO** use proper response codes/bodies so customer can diagnose their own problems and fix them without contacting Azure support or the service team #### JSON Resource Schema & Field Mutability -:white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH response, GET response, and POST response on a given URL path. The PATCH request schema should contain all the same fields with no required fields. -This allows one SDK type for input/output operations and enables the response to be passed back in request. - -> NOTE: A service is not allowed to introduce new required fields or remove any required fields in newer versions of the service. - -This is also not really a rest thing; more of a service implementation thing -While not explicitly defined in JSON, each field in your JSON schema should have an associated mutability rule. REMOVE?: Tools like ADL do allow annotation of mutability, enabling more sophisticated code generation of client libraries. - -:white_check_mark: **DO** create a model of your data types. For each field, apply one of the following rules: +:white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH response, GET response, and POST request/response on a given URL path. The PATCH request schema should contain all the same fields with no required fields. This allows one SDK type for input/output operations and enables the response to be passed back in a request. +:white_check_mark: **DO** think about your resource's fields and how they are used: Field Mutability | Service Request's behavior for this field -----| ---- +-----------------| ----------------------------------------- **Create** | Service honors field only when creating a resource. Minimize create-only fields so customers don't have to delete & re-create the resource. **Update** | Service honors field when creating or updating a resource -**Read** | Service fails request (or accept if they match what's in the resource); returns these fields in a response +**Read** | Service returns this field in a response. If the client passed a read-only field, the service __must__ fail the request unless the passed-in value matches the resource's current value ---- +> In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically __not__ become a nullable filed in a SDK's data structure. THis allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. -#### General guidelines -The following are general guidelines when using REST: +:white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. + +:white_check_mark: **DO** treat JSON field names with case-sensitivity. -:white_check_mark: **DO** serve GET for resource retrieval and send JSON in the response body. +:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions but avoid if at all possible. + +:white_check_mark: **DO** use GET for resource retrieval and return JSON in the response body :white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. @@ -330,49 +331,42 @@ The following are general guidelines when using REST: :white_check_mark: **DO** use DELETE to remove a resource. -:white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. - -:white_check_mark: **DO** treat JSON field names with case-sensitivity. - -:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions but avoid if at all possible. - -:white_check_mark: **DO** fail an operation with ```400-Bad Request``` if the request JSON body is improperly-formed JSON. - -:white_check_mark: **DO** fail an operation with ```412-Unprocessable Entity``` if any JSON field name or value is not fully understood by the specific version of the service. +:white_check_mark: **DO** fail an operation with ```400-Bad Request``` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response (TODO: link) indicating what is wrong so customer can diagnose the issue and fix it themselves. -:no_entry: **DO NOT** return secret fields via GET. For example, do not return adminPassword in JSON. - -:no_entry: **DO NOT** add computed output values if the computed value can be calculated from other information in the payload. It unnecessarily expands the payload. +:no_entry: **DO NOT** return secret fields via GET. For example, do not return ```administratorPassword``` in JSON. :heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. +:no_entry: **DO NOT** add fields to the JSON if the value is easily computable from other fields to avoid bloating the body. -#### Process a PATCH/PUT request -PATCH/PUT requests accept a subset of fields. Because of this, they require additional guidelines handling requests and responses. In general, you want to avoid creating partial resources as a result of create operations. - -:white_check_mark: **DO** adhere to the return codes in the following table when implementing your API. These tests be processed in this oder: +:white_check_mark: **DO** follow the processing below to create/update/replace a resource: -When using this method |if this condition happens | use this response code -----|----|---- -PATCH/PUT | Any JSON field name/value not known/valid | 422-Unprocessable Entity -PATCH/PUT | Any Read field passed (client can't set Read fields) | 422-Unprocessable Entity -| **IF the resource does not exist** | -PATCH/PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity -PATCH/PUT | Create resource using Create/Update fields |201-Created -| **If the resource already exists** | -PATCH | Any Create field doesn't match current value (allows retries) |409-Conflict +When using this method | if this condition happens | use this response code +---------------------- | ------------------------- | ---------------------- +PATCH/PUT | Any JSON field name/value not known/valid | 400-Bad Request +PATCH/PUT | Any Read field passed (client can't set Read fields) | 400-Bad Request +| **If the resource does not exist** | +PATCH/PUT | Any mandatory Create/Update field missing | 400-Bad Request +PATCH/PUT | Create resource using Create/Update fields | 201-Created +| **If the resource already exists** | +PATCH | Any Create field doesn't match current value (allows retries) | 409-Conflict PATCH | Update resource using Update fields | 200-OK -PUT | Any mandatory Create/Update field missing | 422-Unprocessable Entity +PUT | Any mandatory Create/Update field missing | 400-Bad Request PUT | Overwrite resource entirely using Create/Update fields | 200-OK #### Handling Errors -When your service encounters an error, you will not be able to return the payload that was sent as part of the operation. Because you cannot put a resource in the response, you will instead use a specific header, ```x-ms-error-code``` along with a string code. In addition, the message body will have the descriptive text of the error. This error message should give enough information to the customer so they can self-diagnose the problem. It is preferrable to include additional information as part the 'inner-error'. Informative error codes and messages increase the ability for customers to be successful and lowers the overall support costs for your service. The code value that is passed in the header is also repeated as the ```code``` value in the error. It is possible that clients can recover from errors gracefully at runtime. Often, the mechanism employed will be to inspect the header value and implement appropriate coping logic. Because of this, the error code, is considered part of your API contract. Example: +There are 2 kinds of errors: + - An error where you expect customer code to gracefully recover at runtime + - An error indicating a bug in customer code that is unlikely to be recoverable at runtime; the customer must just fix their code -**HEADER** +:white_check_mark: **DO** return error an ```x-ms-error-code``` response header with a string value indicating what went wrong. +> NOTE: String values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future. -```x-ms-error-code``` : ```InvalidPasswordFormat``` +:white_check_mark: **DO** carefully craft ```x-ms-error-code``` string values for errors that are recoverable at runtime. -**RESPONSE BODY** +:heavy_check_mark: **YOU MAY** group common customer code errors into a few ```x-ms-error-code``` string values. + +:white_check_mark: **DO** provide a response body as follows: ```json { "error": { @@ -387,50 +381,46 @@ When your service encounters an error, you will not be able to return the payloa } ``` -:white_check_mark: **DO** Return x-ms-error-code header with string - -:white_check_mark: **DO** return an ```error``` as part of the response body. The `code` field of the `error` object must match the ```x-ms-error-code```. - -:white_check_mark: **DO** document runtime errors that are recoverable. - -:no_entry: **DO NOT** change the value of ```code``` between versions--it is part of your API contract and is considered a breaking change. - -:heavy_check_mark: **YOU MAY** change the values of all other fields. +:white_check_mark: **DO** ensure that the top-level ```code``` field's value is identical to the ```x-ms-error-code``` header's value. +:heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are __not__ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. +:white_check_mark: **DO** document the service's error code strings; they are part of the API contract. ### JSON Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: -* **Boolean:** true/false -* **Number:** signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) -* **String:** used for everything else + + Type | Description + ---- | ----------- + Boolean | true/false (always lowercase) + Number | Signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) + String | Used for everything else :white_check_mark: **DO** use integers within the acceptable range of JSON number. -#### String Contracts -When using strings, you must establish, and adhere to, a well defined contract for the format. For example, you should be cognizant of attributes like maximum length, legal characters, case-sensitivity, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. +:white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. -:white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable." +:white_check_mark: **DO** use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC3339 for date/time. + +:white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable" across multiple programming languages. :white_check_mark: **DO** use [RFC3339] for date/time. :white_check_mark: **DO** use [RFC4122] for UUIDs. -##### Composite types -JSON also supports composing strings into higher order constructs, for example: -* **Object**: { "name" : value, … } -* **Array**: [ value, … ] +:heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. -:warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with updates when using ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. +:warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. -:ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays. +:heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. -#### Enums & SDKs (Client libraries) +:ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays whenever possible. +#### Enums & SDKs (Client libraries) It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI definition as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation. -However, it is not uncommon for the set of values to grow over the life of a service. For this reason, Microsoft's tooling uses the concept of an "extensible enum," which indicates that the set of values should be treats as only a *partial* list. -This indicates to client libraries that values of the enumeration field should be effectively treated as strings. This enables the set of values to grow over time while ensuring stability in client libraries. +However, it is not uncommon for the set of values to grow over the life of a service. For this reason, Microsoft's tooling uses the concept of an "extensible enum," which indicates that the set of values should be treated as only a *partial* list. +This indicates to client libraries and customers that values of the enumeration field should be effectively treated as strings and that undocumented value may returned in the future. This enables the set of values to grow over time while ensuring stability in client libraries and customer code. :white_check_mark: **DO** use "extensible enums" @@ -438,52 +428,273 @@ This indicates to client libraries that values of the enumeration field should b :no_entry: **DO NOT** send "enum integers" over the wire. -:no_entry: **DO NOT** remove values from your enumeration list. This will likely result in a breaking change to client libraries & customers. +:no_entry: **DO NOT** remove values from your enumeration list as this breaks customer code. #### Polymorphic types -While polymorphism is a powerful concept in programming languages, returing "polymorphic JSON" as part of an API introduces significant complexity for developers of client libraries and applications, especially as new versions of your service are introduced. For example, consider a service where V1 introduces two shapes, Retangles and Circles. They could be represented in JSON as follows: +:warning: **YOU SHOULD NOT** use polymorphic JSON types because they greatly complicate the customer code due to runtime dynamic casts and the introduction of new types in the future. If you can't avoid them, then follow these guidelines: + +:white_check_mark: **DO** define a ```kind``` field indicating the kind of the resource and include any kind-specific fields in the body. Below is an example of JSON for a Rectangle and Circle: **Rectangle** ```json -{"kind": "rectangle", -"x": 100, "y": 50, "width": 10, "length": 24, "fillColor": "Red", "lineColor": "White", -"subscription": {"expiration": "2024" "kind": "free"}} +{ + "kind": "rectangle", + "x": 100, + "y": 50, + "width": 10, + "length": 24, + "fillColor": "Red", + "lineColor": "White", + "subscription": { + "kind": "free" + } +} ``` **Circle** ```json - {"kind": "circle", -"x": 100, "y": 50, "radius": 10, "fillColor": "Green", "lineColor": "Black", -"subscription": { "expiration": "2024", "kind": "paid", "invoice": "123456"}}`` +{ + "kind": "circle", + "x": 100, + "y": 50, + "radius": 10, + "fillColor": "Green", + "lineColor": "Black", + "subscription": { + "kind": "paid", + "expiration": "2024", + "invoice": "123456" + } +} ``` -The first issue is that developers writing code against this JSON contract will have a very difficult time, especially in typed languages. It will be impossible to determine what the actual type is during development, minimizing the effectiveness of tooling. At runtime, developers will have to parse the JSON, interpret the "kind" value, and *then* cast to the proper sub-class. - -Overall, this is a very brittle design that leads to a poor developer experience, especially over time. Consider the scenario when a new shape is introduced in V2 of the API. Existing client libraries that work with V1 will have no concept of this new shape and, when receiving an unknown shape, fail. - -:warning: **YOU SHOULD NOT** use polymorphic types. Instead, return concrete types. +> Both Rectangle and Circle has common fields: ```kind```, ```fillColor```, ```lineColor```, and ```subscription```. A Rectangle also has ```x```, ```y```, ```width```, and ```length``` while a Circle has ```x```, ```y```, and ```radius```. The ```subscription``` is a nested polymorphic type. A ```free``` subscription has no additional fields and a ```paid``` subscription has ```expiration``` and ```invoice``` fields. ## Common API Patterns ### Performing an Action -The REST specificaiton is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services have the requirement to perform an action on a resource, e.g. getting the thumbnail of an image, sending an SMS message. To perform an action on a resource, we introduce the concept of a "controller resource," or simply "controller." -> A controller resource models a procedural concept. Controller resources are like executable functions, with parameters and return values; inputs and outputs.[REST API Tutorial](https://restfulapi.net/resource-naming/) +The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image, sending an SMS message. -One reason for using controllers is API consistency. The URL to the controller will include the resource upon which the operation will take place. Typically, the URL pattern for actions should be constructed as follows: First, specify the service and the resource collection. Next, identify the exact resource to operate on using the resource's id. Finally, use the "/:" pattern to indicate the action on the resource. Query parameters are used to send any parameters to the action. Consider the example of a service that sends a text message. The action URl would look similar to the following: +:white_check_mark: **DO** pattern your URL like this to perform an action on a resource > **URL Pattern** > -> ```https://///:?``` +> ```https://...///:?``` > > **SMS Example** > -> ```https://myTextingService/users/12345/:send-sms?Text="Hello"``` +> ```https://.../users/Bob/:send-sms?Text="Hello"``` -While this is a generally recognized pattern in the industry, these Azure API guidelines add further precision to how this pattern should be modeled. +> **Equivalent to (in C#)** +> +> ```users["Bob"].SendSms("Hello")``` -:white_check_mark: **DO** use a POST operation for any action on a resource. Further, this operation **MUST** be idempotent. +:white_check_mark: **DO** use a POST operation for any action on a resource. :ballot_box_with_check: **YOU SHOULD** use a verb to name your action. +:white_check_mark: **DO** support the Repeatability-Request-ID & Repeatability-First-Sent request headers if the action needs to be idempotent if retries occur. + +### Collections +:heavy_check_mark: **YOU MAY** expose an operation that lists your resources by supporting a GET method with a URL to a resource-collection (as opposed to a resource-id). + +:ballot_box_with_check: **YOU SHOULD** support paging today if there is ever a chance in the future that the number of items can grow to be very large. +> NOTE: It is a breaking change to add paging in the future + +:white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources. + +> **Example Response Body** +> ``` +> { +> "value": [ +> { "id": "Item 01", "etag": "0xabc", "price": 99.95, "sizes": null }, +> { … }, +> { … }, +> { "id": "Item 99", "etag": "0xdef", "price": 59.99, "sizes": null } +> ], +> "nextLink": "{opaqueUrl}" +> } +>``` + +:ballot_box_with_check: **YOU SHOULD** use ```value``` as the name of the top-level array field unless a more appropriate name is available. + +:white_check_mark: **DO** include the id field and etag field (if supported) for each item as this allows the customer to modify the item in a future operation. + +:white_check_mark: **DO** return a ```nextLink``` field with a URL that the client can GET in order to retrieve the next page of the collection. + +:no_entry: **DO NOT** return the ```nextLink``` field at all when returning the last page of the collection. + +:no_entry: **DO NOT** ever return a ```nextLink``` field with a value of null. + +:white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). + + +#### Query options +:heavy_check_mark: **YOU MAY** support the following query parameters allowing customers to control the list operation: +Parameter name | Type | Description +-------------- | ---- | ----------- +_filter_ | string | an expression on the resource type that selects the resources to be returned +_orderby_ | string array | a list of expressions that specify the order of the returned resources +_skip_ | integer | an offset into the collection of the first resource to be returned +_top_ | integer | the maximum number of resources to return from the collection +_maxpagesize_ | integer | the maximum number of resources to include in a single response +_select_ | string array | TODO +_expand_ | string array | TODO + +:white_check_mark: **DO** return an error if the client specifies any parameter not supported by the service. + +:white_check_mark: **DO** treat these query parameter names as case-sensitive. + +:no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). + +:white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. + +:white_check_mark: **DO** apply _select_ or _expand_ options after applying all the query options in the table above. + +#### filter + +:heavy_check_mark: **YOU MAY** support filtering of the results of a list operation with the _filter_ query parameter. + +The value of the _filter_ option is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response. + +:white_check_mark: **DO** omit all resources from the collection for which the _filter_ expression evaluates to false or to null, or references properties that are unavailable due to permissions. + +Example: return all Products whose Price is less than $10.00 + +```http +GET https://api.contoso.com/products?filter=price lt 10.00 +``` + +##### filter operators + +:heavy_check_mark: **YOU MAY** support the following operators in _filter_ expressions: +Operator | Description | Example +-------------------- | --------------------- | ----------------------------------------------------- +__Comparison Operators__ | | +eq | Equal | city eq 'Redmond' +ne | Not equal | city ne 'London' +gt | Greater than | price gt 20 +ge | Greater than or equal | price ge 10 +lt | Less than | price lt 20 +le | Less than or equal | price le 100 +__Logical Operators__ | | +and | Logical and | price le 200 and price gt 3.5 +or | Logical or | price le 3.5 or price gt 200 +not | Logical negation | not price le 3.5 +__Grouping Operators__ | | +( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 + +:white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if a client includes an operator in a _filter_ expression that is not supported by the operation. + +:white_check_mark: **DO** use the following operator precedence for supported operators when evaluating _filter_ expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: + +| Group | Operator | Description +| ----------------|----------|------------ +| Grouping | ( ) | Precedence grouping | +| Unary | not | Logical Negation | +| Relational | gt | Greater Than | +| | ge | Greater than or Equal | +| | lt | Less Than | +| | le | Less than or Equal | +| Equality | eq | Equal | +| | ne | Not Equal | +| Conditional AND | and | Logical And | +| Conditional OR | or | Logical Or | + +##### Operator examples +The following examples illustrate the use and semantics of each of the logical operators. + +Example: all products with a name equal to 'Milk' + +```http +GET https://api.contoso.com/products?filter=name eq 'Milk' +``` + +Example: all products with a name not equal to 'Milk' + +```http +GET https://api.contoso.com/products?filter=name ne 'Milk' +``` + +Example: all products with the name 'Milk' that also have a price less than 2.55: + +```http +GET https://api.contoso.com/products?filter=name eq 'Milk' and price lt 2.55 +``` + +Example: all products that either have the name 'Milk' or have a price less than 2.55: + +```http +GET https://api.contoso.com/products?filter=name eq 'Milk' or price lt 2.55 +``` + +Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: + +```http +GET https://api.contoso.com/products?filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 +``` + +#### orderby + +:heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the _orderby_ query parameter. +> NOTE: It is unusual for a service to support __orderby__ because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results. + +The value of the _orderby_ parameter is a comma-separated list of expressions used to sort the items. +A special case of such an expression is a property path terminating on a primitive property. + +Each expression in the _orderby_ parameter value may include the suffix "asc" for ascending or "desc" for descending, separated from the expression by one or more spaces. + +:white_check_mark: **DO** sort the collection in ascending order on an expression if "asc" or "desc" is not specified. + +:white_check_mark: **DO** sort NULL values as "less than" non-NULL values. + +:white_check_mark: **DO** sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on. + +:white_check_mark: **DO** use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically. + +:white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if the client requests sorting by a field that is not supported by the operation. + +For example, to return all people sorted by name in ascending order: +```http +GET https://api.contoso.com/people?$orderBy=name +``` + +For example, to return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. +```http +GET https://api.contoso.com/people?$orderBy=name desc,hireDate +``` + +Sorting MUST compose with filtering such that: +```http +GET https://api.contoso.com/people?filter=name eq 'david'&orderby=hireDate +``` +will return all people whose name is David sorted in ascending order by hireDate. + +##### Considerations for sorting with pagination + +:white_check_mark: **DO** use the same filtering options and sort order for all pages of a paginated list operation response. + +##### skip + +:heavy_check_mark: **YOU MAY** allow clients to pass the _skip_ query parameter to specify an offset into collection of the first resource to be returned. + +:white_check_mark: **DO** define the _skip_ parameter as an integer with a default and minimum value of 0. + +##### top + +:heavy_check_mark: **YOU MAY** allow clients to pass the _top_ query parameter to specify the maximum number of resources to return from the collection. + +:white_check_mark: **DO** define the _top_ parameter as an integer with a minimum value of 1. If not specified, __top__ has a default value of infinity. + +:white_check_mark: **DO** return the collection's _top_ number of resources (if available), starting from _skip_. + +##### maxpagesize + +:heavy_check_mark: **YOU MAY** allow clients to pass the _maxpagesize_ query parameter to specify the maximum number of resources to include in a single page response. + +:white_check_mark: **DO** define the _maxpagesize_ parameter as an optional integer with a default value appropriate for the collection. + +:white_check_mark: **DO** make clear in documentation of the _maxpagesize_ parameter that the operation may choose to return fewer resources than the value specified. + ### API Versioning Azure services need to change over time. However, when changing a service, there are 2 requirements: @@ -518,7 +729,7 @@ PUT https://service.azure.com/users/Jeff?api-version=2021-06-04-preview :no_entry: **DO NOT** introduce any breaking changes into service. > NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy/) has tables (section 5) describing what kinds of changes are considered breaking. If a new service version must break customers (due to security/compliance/etc.), contact the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) as soon as possible. -#### Use extensible enums +#### Use Extensible Enums While removing a value from an enum is a breaking change, adding value to an enum can be handled with an _extensible enum_. An extensible enum is a string value that has been marked with a special marker - setting `modelAsString` to true within an `x-ms-enum` block. For example: @@ -541,7 +752,7 @@ While removing a value from an enum is a breaking change, adding value to an enu :ballot_box_with_check: **DO** model an ```enum``` as a string unless you are positive that the symbol set will **NEVER** change over time. -#### Version discovery +#### Version Discovery Simpler clients may be hardcoded to a single version of a service. Since Azure services offer each version for a well-known period of time, a client that’s regularly maintained can be always operational without further complexity as long as during regular maintenance the client is moved forward to new versions in advance of older ones being retired. @@ -612,7 +823,7 @@ The Microsoft REST API guidelines for Long Running Operations are an updated, cl :white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. -### Bring your own storage +### Bring your own Storage When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS will provide the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders, nor do they have extensions. :white_check_mark: **DO** use Azure Bring Your Own Storage. @@ -628,7 +839,7 @@ How you secure and protect the data and files that your service uses will not on :white_check_mark: **DO** Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. -##### Handlilng 'downstream' errors +##### Handling 'downstream' errors It is not uncommon to rely on other services, e.g. storage, when implementing your service. Inevitably, the services you depend on will fail. In these situations, you can include the downstream erorr code and text in the inner-error of the response body. This provides a consistent pattern for handling errors in the services you depend upon. :white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. @@ -647,11 +858,11 @@ Desiging an API for accessing a single file, depending on your scenario, is rela :ballot_box_with_check: **YOU SHOULD** support managed identity using Azure Storage by default (if using Azure services). -###### File versioning +###### File Versioning Depending on your requirements, there are scenarios where users of your service will require a specific version of a file. For example, you may need to keep track of configuration changes over time to be able to rollback to a previous state. In these scenarios, you will need to provide a mechanism for accessing a specific version. :white_check_mark: **DO** Enable the customer to provide an ETag to specify a specific version of a file. -##### File collections +##### File Collections When your users need to work with multiple files, for example a document translation service, it will be important to provide them access to the collection, and its contents, in a consistent manner. Because there is no industry standard for working with with containers, these guidelines will recommend that you leverage Azure Storage. Following the guidelines above, you also want to ensure that you don't expose file system constructs, e.g. folders, and instead use storage constructs, e.g. blob prefixes. :white_check_mark: **DO** When using a Shared Access Signature (SAS), ensure this is assigned to the container and that the permissions apply to the content as well. @@ -697,10 +908,10 @@ When designing an API, you will almost certainly have to manage how your resourc :ballot_box_with_check: **YOU SHOULD** use ```etags``` consistently across your API, i.e. if you use an ```ETag```, accept it on all other operations. > You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). -#### Cache control +#### Cache Control One of the more common uses for ```ETag``` headers is cache control, also referred to a "conditional GET." This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). That is, using the value of the ```ETag``` , the server can determine if the resource has changed. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. -Implementing this strategy is relatively straightforward. First, you will return an ```ETag``` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your ```ETag```. In these scenarios, when a request is made by the client an ```ETag``` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The ```ETag``` value can then be sent in subsequent requests as part of the ```if-none-match``` header. This tells the service to compare the ```ETag``` that came in with the request, with the latest value that it has calcuted. If the two values are the same, then it is not necesary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated ```ETag``` value in the header. +Implementing this strategy is relatively straightforward. First, you will return an ```ETag``` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your ```ETag```. In these scenarios, when a request is made by the client an ```ETag``` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The ```ETag``` value can then be sent in subsequent requests as part of the ```if-none-match``` header. This tells the service to compare the ```ETag``` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated ```ETag``` value in the header. :ballot_box_with_check: **YOU SHOULD** implement conditional read strategies @@ -713,10 +924,9 @@ Implementing this strategy is relatively straightforward. First, you will return > For more control over caching, please refer to the ```cache-control``` [HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). -#### Optimistic concurrency +#### Optimistic Concurrency An ```ETag``` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of ```ETags``` and the HTTP. - :warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. :white_check_mark: **DO** adhere to the following table for guidance: @@ -725,7 +935,7 @@ An ```ETag``` should also be used to reflect the create, update, and delete poli These guidelines describe the upfront design considerations, technology building blocks, and common patterns that Azure teams encounter when building an API for their service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. -### Typical review session +### Typical Review Session When engaging with the API REST Stewardship board, your working sessions will generally focus on three areas: * Correctness - Your service should leverage the proper HTTP verbs, return codes, and respect the core constructs of a REST API, e.g. idempotency, that are standard throughout the industry. * Consistency - Your services should look and behave as though they are natural part of the Azure platform. From c7a3e93e51a57249c6416d0945ba1723e39f0944 Mon Sep 17 00:00:00 2001 From: Johan Stenberg Date: Mon, 10 May 2021 19:02:17 -0700 Subject: [PATCH 123/729] Added guidance for repeatable requests --- azure/Guidelines.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index cf23c364..59c4ac06 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -794,6 +794,16 @@ Content-Length: 0 Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. +## Repeatability of requests + +The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. + +A service **SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). + +- The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. +- A service advertises support for repeatability requests by adding the `Repeatbility-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. +- When understood, all endpoints co-located behind a DNS name **MUST** understand the header. This means that a service **MUST NOT** ignore the presence of a header for any endpoints behind the DNS name, but rather fail the request containing a `Repeatability-Request-ID` header if that particular endpoint lacks support for repeatable requests. Such partial support **SHOULD** be avoided due to the confusion it causes for clients. + ##### Additional References From 168d976e3b634b04f3a35dfd5eeb00f0979323f0 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 28 Jun 2021 20:14:17 -0700 Subject: [PATCH 124/729] Fix some links and typos --- azure/Guidelines.md | 105 ++++++++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 59c4ac06..123ce6bd 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -90,11 +90,13 @@ Focusing on hero scenarios reduces development, support, and maintenance costs; ### Start with your API Definition Understanding how your service is used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It reflects the abstractions & naming decisions and makes it easy for developers to implement the hero scenarios. -:white_check_mark: **DO** provide an [OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. +:white_check_mark: **DO** provide an [OpenAPI Definition][OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. + ### Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. @@ -185,7 +187,7 @@ Examples: - Response header ([RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4)): `content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2` - GUID format: `https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2` -:white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for or always a single verified domain. +:white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for `` or always a single verified domain. :heavy_check_mark: **YOU MAY** use URLs as values ``` @@ -205,7 +207,7 @@ Cloud applications embrace failure. Therefore, to enable customers to write faul :ballot_box_with_check: **YOU SHOULD** use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent. -:heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response __must__ return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (TODO: See link) +:heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response __must__ return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#Repeatability-of-requests)). :white_check_mark: **DO** adhere to the return codes in the following table when the method is successful: @@ -229,7 +231,7 @@ DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found ### HTTP Query Parameters and Header Values Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. -:white_check_mark: **DO** validate all query parameter and request header values and return an error response if any value fails validation. +:white_check_mark: **DO** validate all query parameter and request header values and fail the operation with ```400-Bad Request``` if any value fails validation. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. :white_check_mark: **DO** use the following table when translating strings: @@ -246,29 +248,27 @@ Byte array | Base-64 encoded, max length TODO: Expand the explanation for numbers. -TODO: Fix the links. - The table below lists the headers most used by Azure services: Header Key | Applies to | Example ------------------- | ---------- | ------------- *authorization* | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) -*x‑ms‑useragent* | Request | [see Telemetry](http://TODO:link-goes-here) -traceparent | Request | [see Distributed Tracing](http://TODO:link-goes-here) -tracecontext | Request | [see Distributed Tracing](http://TODO:link-goes-here) +*x-ms-useragent* | Request | see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry) +traceparent | Request | see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry) +tracecontext | Request | see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry) accept | Request | application/json -if-match | Request | "67ab43" or * (no quotes) (see Conditional Access) -if-none-match | Request | "67ab43" or * (no quotes) [see Conditional Access](http://TODO:link-goes-here) -If-Modified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) [see Optimistic Concurrency](http://TODO:link-goes-here) -If‑Unmodified‑Since | Request | (RFC1123) [see Optimistic Concurrency](http://TODO:link-goes-here) -date [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123) | Both | Sun, 06 Nov 1994 08:49:37 GMT +if-match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) +if-none-match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) +If-Modified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) +If-Unmodified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) +date | Both | Sun, 06 Nov 1994 08:49:37 GMT (see [RFC7231, Section 7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2)) *content-type* | Both | application/merge-patch+json *content-length* | Both | 1024 *x-ms-request-id* | Response | [see Customer Support](http://TODO:link-goes-here) -etag | Response | "67ab43" [see Conditional Access](http://TODO:link-goes-here) -last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) -*x-ms-error-code* | Response | [see Processing a REST Request](http://TODO:link-goes-here) -retry-after | Response | 180 [see Throttling Client Requests] +etag | Response | "67ab43" see [Conditional Requests](#Conditional-Requests) +last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT +*x-ms-error-code* | Response | see [Handling Errors](#Handling-Errors) +retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)) :white_check_mark: **DO** support all headers shown in *italics* @@ -278,6 +278,8 @@ retry-after | Response | 180 [see Throttling Client Requests] :white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it +:white_check_mark: **DO** accept and return date values in headers using the HTTP Date format as defined in [RFC 7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT" + :no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated :no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. @@ -331,7 +333,7 @@ Field Mutability | Service Request's behavior for this field :white_check_mark: **DO** use DELETE to remove a resource. -:white_check_mark: **DO** fail an operation with ```400-Bad Request``` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response (TODO: link) indicating what is wrong so customer can diagnose the issue and fix it themselves. +:white_check_mark: **DO** fail an operation with ```400-Bad Request``` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. :no_entry: **DO NOT** return secret fields via GET. For example, do not return ```administratorPassword``` in JSON. @@ -537,8 +539,8 @@ _orderby_ | string array | a list of expressions that specify the orde _skip_ | integer | an offset into the collection of the first resource to be returned _top_ | integer | the maximum number of resources to return from the collection _maxpagesize_ | integer | the maximum number of resources to include in a single response -_select_ | string array | TODO -_expand_ | string array | TODO +_select_ | string array | a list of field names to be returned for each resource +_expand_ | string array | a list of the related resources to be included in line with each resource :white_check_mark: **DO** return an error if the client specifies any parameter not supported by the service. @@ -794,33 +796,18 @@ Content-Length: 0 Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. -## Repeatability of requests +### Repeatability of requests The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. A service **SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). - The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. -- A service advertises support for repeatability requests by adding the `Repeatbility-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. +- A service advertises support for repeatability requests by adding the `Repeatability-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. - When understood, all endpoints co-located behind a DNS name **MUST** understand the header. This means that a service **MUST NOT** ignore the presence of a header for any endpoints behind the DNS name, but rather fail the request containing a `Repeatability-Request-ID` header if that particular endpoint lacks support for repeatable requests. Such partial support **SHOULD** be avoided due to the confusion it causes for clients. -##### Additional References - - -[1]: https://github.com/microsoft/api-guidelines -[RFC2557]: https://www.ietf.org/rfc/rfc2557.txt - - -[2]: https://aka.ms/armwiki -[3]: https://github.com/Azure/azure-resource-manager-rpc - -[OpenAPI Specification]: https://github.com/Azure/adx-documentation-pr/wiki/Getting-started-with-OpenAPI-specifications - - -[6]: https://support.microsoft.com/en-us/help/30881 -[7]: http://aka.ms/aprwiki - +[OpenAPI Specification]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md ### Long Running Operations & Jobs @@ -850,7 +837,7 @@ How you secure and protect the data and files that your service uses will not on :white_check_mark: **DO** Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. ##### Handling 'downstream' errors -It is not uncommon to rely on other services, e.g. storage, when implementing your service. Inevitably, the services you depend on will fail. In these situations, you can include the downstream erorr code and text in the inner-error of the response body. This provides a consistent pattern for handling errors in the services you depend upon. +It is not uncommon to rely on other services, e.g. storage, when implementing your service. Inevitably, the services you depend on will fail. In these situations, you can include the downstream error code and text in the inner-error of the response body. This provides a consistent pattern for handling errors in the services you depend upon. :white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. @@ -917,7 +904,8 @@ When designing an API, you will almost certainly have to manage how your resourc :ballot_box_with_check: **YOU SHOULD** use ```etags``` consistently across your API, i.e. if you use an ```ETag```, accept it on all other operations. -> You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). +You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). + #### Cache Control One of the more common uses for ```ETag``` headers is cache control, also referred to a "conditional GET." This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). That is, using the value of the ```ETag``` , the server can determine if the resource has changed. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. @@ -932,15 +920,46 @@ Implementing this strategy is relatively straightforward. First, you will return | etag value = if-none-match value | 304 Not Modified | no additional information | | etag value != if-none-match value | 200 OK | Response body include the serialized value of the resource (typically JSON) | -> For more control over caching, please refer to the ```cache-control``` [HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). +For more control over caching, please refer to the ```cache-control``` [HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). #### Optimistic Concurrency -An ```ETag``` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of ```ETags``` and the HTTP. +An ```ETag``` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of ```ETags``` and the [HTTP Request / Response Pattern](#http-request--response-pattern). :warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. :white_check_mark: **DO** adhere to the following table for guidance: +| Operation | Header | Value | etag check | Return code | Response | +|:------------|:--------------|:------|:-----------|:------------|----------------| +| PATCH / PUT | if-none-match | * | check for *any* version of the resource ('*' is a wildcard used to match anything), if none are found, create the resource. | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | +| PATCH / PUT | if-none-match | * | check for *any* version of the resource, if one is found, fail the operation | 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| +| PATCH / PUT | if-match | value of etag | value of if-match equals the latest etag value on the server, confirming that the version of the resource is the most current | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | +| PATCH / PUT | if-match | value of etag | value of if-match header DOES NOT equal the latest etag value on the server, indicating a change has ocurred since after the client fetched the resource| 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| +| DELETE | if-none-match | value of etag | value does NOT match the latest value on the server | 412 Preconditioned Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request. | +| DELETE | if-none-match | value of etag | value matches the latest value on the server | 200 OK or
204 No Content | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request. | + +#### Computing ETags +The strategy that you use to compute the ```ETag``` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the ```ETag```. Another common strategy for determining the value of an ```ETag``` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. + +:heavy_check_mark: **YOU MAY** use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. + +:ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. + +:heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. + +### Distributed Tracing & Telemetry +Azure SDK client guidelines specify that client libraries must send telemetry data through the ```User-Agent``` header, ```X-MS-UserAgent``` header, and Open Telemetry. +Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. + +:white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. + +:no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. +#### Additional References +* [Azure SDK client guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) +* [Azure SDK User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) +* [Azure SDK Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) +* [Open Telemetry](https://opentelemetry.io/) + ## Final thoughts These guidelines describe the upfront design considerations, technology building blocks, and common patterns that Azure teams encounter when building an API for their service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. From b6e470f1d09324c40b9d53633df361828396eff5 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 29 Jun 2021 09:19:32 -0400 Subject: [PATCH 125/729] clean up of inline comments --- azure/Guidelines.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 123ce6bd..6f438344 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -92,12 +92,6 @@ Understanding how your service is used and defining its model and interaction pa :white_check_mark: **DO** provide an [OpenAPI Definition][OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. - - ### Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. From 5360444724ea30dff00d271d1607d3c20a193d47 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 29 Jun 2021 10:39:42 -0400 Subject: [PATCH 126/729] Removed "Background" section at the top. --- azure/Guidelines.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 6f438344..f54994fd 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,12 +1,3 @@ -> ### Background -> This document is a work in progress and is intended to become an updated version of the Azure REST API guidelines. It is based on the best practices for building REST APIs, the existing Microsoft and Azure API Guidelines, and feedback from the API Stewardship Board. Your thoughts, comments, issues, pull requests, and all other forms of feedback are welcomed and encouraged. You can also reach out to Mark Weitzel as well. -> -> Thanks! - - ---- -
- # Microsoft Azure HTTP/REST API Guidelines ## Introduction From 593dc8a2295cff6b44dbf78f6682b0e41e95afd7 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 2 Jul 2021 15:27:14 -0400 Subject: [PATCH 127/729] Clean up & refactor advice section (#260) --- azure/ConsiderationsForServiceDesign.md | 97 ++++ azure/Guidelines.md | 638 ++++++++++-------------- 2 files changed, 364 insertions(+), 371 deletions(-) create mode 100644 azure/ConsiderationsForServiceDesign.md diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md new file mode 100644 index 00000000..65aa4343 --- /dev/null +++ b/azure/ConsiderationsForServiceDesign.md @@ -0,0 +1,97 @@ +## Considerations for Service Design +Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, especially over time. + +This document provides Microsoft teams building Azure services with a set of guidelines that help service teams build great APIs. The guidelines create APIs that are approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault-tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation enabling a broad audience of developers across multiple languages. + +Azure Service teams should engage the Azure HTTP/REST Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. + +Your goal is to create a developer friendly API where: + +> :white_check_mark: **DO** ensure that customer workloads never break +> +> :white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library without requiring code changes + +### Azure Management Plane vs Data Plane +*Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review.* + +A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful to both administrators and developers. In this case, the operation can appear in both APIs. Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on the conventions of the Azure ARM. + +### Start with the Developer Experience +A great API starts with a well thought out and designed service. Your service should define simple/understandable abstractions with each given a clear name that you use consistently throughout your API and documentation. There must also be an unambiguous relationship between these abstractions. + +It is extremely difficult to create an elegant API that works well on top of a poorly designed service; the service team and customers will live with this pain for years to come. So, the service team should empathize with customers by: + - Building apps that consume the API + - Hold reviews and share what is learned with your team + - Get customer feedback from API previews + - Thinking about the code that a customer writes both before and after an HTTP operation + - Initializing and reading from the data structures your service requires + - Thinking about which errors are recoverable at runtime as opposed to indicating a bug in the customer code that must be fixed + +The whole purpose of a preview to address feedback by improving abstractions, naming, relationships, API operations, and so on. It is OK to make breaking changes during a preview to improve the experience now so that it is sustainable long term. + +> :white_check_mark: **DO** provide examples in multiple languages +> +> :white_check_mark: **DO** include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). + +### Focus on Hero Scenarios +It is important to realize that writing an API is, in many cases, the easiest part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, client libraries, examples, blog posts, videos, and supporting customers in perpetuity. In fact, implementing an API is of miniscule cost compared to all the other downstream activities. + +*For this reason, it is much better to ship with fewer features and only add new features over time as required by customers.* + +Focusing on hero scenarios reduces development, support, and maintenance costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A telltale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. + +> :white_check_mark: **DO** define "hero scenarios" first including abstractions, naming, relationships, and then define the API describing the operations required +> +> :white_check_mark: **DO** provide example code demonstrating the "Hero Scenarios" +> +> :no_entry: **DO NOT** proactively add APIs for speculative features customers might want + +### Start with your API Definition +Understanding how your service is used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It reflects the abstractions & naming decisions and makes it easy for developers to implement the hero scenarios. + +> :white_check_mark: **DO** provide an [OpenAPI Definition](https://swagger.io/specification/) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. + +### Use Previews to Iterate + Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. + +> :ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. +> +> :ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. +> +> :ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. +> +> :ballot_box_with_check: **YOU SHOULD** consider doing a *code with* exercise in which you actively develop with the customer, observing and learning from their API usage. +> +> :ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. + +### Avoid Surprises +A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. + +> :ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint __SHOULD__ work with a single type to avoid problems during SDK creation. +> +> :ballot_box_with_check: **YOU SHOULD** make [Collections](#Collections) easy to work with. Collections are a common source of review comments. It is important to handle them in a consistent manner within your service. +> +> :ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. +> +> :ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. + +### Design for Change Resiliency +As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. + +> :ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. +> +> :ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. +> +> :ballot_box_with_check: **YOU SHOULD** use wider data types (e.g. 64-bit vs. 32-bit) as they are more future-proof. For example, JavaScript can only support integers up to 253, so relying on the full width of a 64-bit integer should be avoided. + +## Getting Help: The Azure REST API Stewardship Board +The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. + +### Typical Review Session +When engaging with the API REST Stewardship board, your working sessions will generally focus on three areas: +* Correctness - Your service should leverage the proper HTTP verbs, return codes, and respect the core constructs of a REST API, e.g. idempotency, that are standard throughout the industry. +* Consistency - Your services should look and behave as though they are natural part of the Azure platform. +* Well formed - Do your services adhere to REST and Azure standards, e.g. proper return codes, use of headers. +* Durable - Your APIs will grow and change over time and leveraging the common patterns described in this document will help you minimize your tech debt and move fast with confidence. + +It was once said that "all roads lead to Rome." For cloud services, the equivalent might be that "all 'roads' start with your API." That could not be more true than at Microsoft, where client libraries, documentation, and many other artifacts all originate from the fundamental way you choose to expose your service. With careful consideration at the outset of your development effort, the architectural stewardship of the API board, and the thoughtful application of these guidelines, you will be able to produce a consistent, well formed API that will delight our customers. \ No newline at end of file diff --git a/azure/Guidelines.md b/azure/Guidelines.md index f54994fd..508de972 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -13,109 +13,23 @@ These guidelines offer prescriptive guidance that Azure service teams MUST follo Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. -> NOTE: For an existing GA'd service, don't change/break its existing API; instead, leverage these concepts for future APIs while prioritizing consistency within your existing service. +*NOTE: For an existing GA'd service, don't change/break its existing API; instead, leverage these concepts for future APIs while prioritizing consistency within your existing service.* ### Prescriptive Guidance This document offers prescriptive guidance labeled as follows: -:white_check_mark: **DO** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board[TODO: mail link? - not for public people] prior to implementation. - -:ballot_box_with_check: **YOU SHOULD** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. - -:heavy_check_mark: **YOU MAY** consider this pattern if appropriate to your situation. No notification to the Azure HTTP/REST Stewardship Board is required. - -:warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. - -:no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. - +> :white_check_mark: **DO** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board[TODO: mail link? - not for public people] prior to implementation. + +> :ballot_box_with_check: **YOU SHOULD** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. + +> :heavy_check_mark: **YOU MAY** consider this pattern if appropriate to your situation. No notification to the Azure HTTP/REST Stewardship Board is required. + +> :warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. + +> :no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. + *If you feel you need an exception, or need clarity based on your situation, please contact the Azure HTTP/REST Stewardship Board prior to release of your API.* -## Advice for New Services -Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, especially over time. - -This document provides Microsoft teams building Azure services with a set of guidelines that help service teams build great APIs. The guidelines create APIs that are approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault-tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation enabling a broad audience of developers across multiple languages. - -Azure Service teams should engage the Azure HTTP/REST Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. - -Your goal is to create a developer friendly API where: - -:white_check_mark: **DO** ensure that customer workloads never break - -:white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library without requiring code changes - -### Azure Management Plane vs Data Plane -> Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review. - -> A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful to both administrators and developers. In this case, the operation can appear in both APIs. Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on the conventions of the Azure ARM. - -### Start with the Developer Experience -A great API starts with a well thought out and designed service. Your service should define simple/understandable abstractions with each given a clear name that you use consistently throughout your API and documentation. There must also be an unambiguous relationship between these abstractions. - -It is extremely difficult to create an elegant API that works well on top of a poorly designed service; the service team and customers will live with this pain for years to come. So, the service team should empathize with customers by: - - Building apps that consume the API - - Hold reviews and share what is learned with your team - - Get customer feedback from API previews - - Thinking about the code that a customer writes both before and after an HTTP operation - - Initializing and reading from the data structures your service requires - - Thinking about which errors are recoverable at runtime as opposed to indicating a bug in the customer code that must be fixed - -The whole purpose of a preview to address feedback by improving abstractions, naming, relationships, API operations, and so on. It is OK to make breaking changes during a preview to improve the experience now so that it is sustainable long term. - -:white_check_mark: **DO** provide examples in multiple languages - -:white_check_mark: **DO** include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). - -### Focus on Hero Scenarios -It is important to realize that writing an API is, in many cases, the easiest part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, client libraries, examples, blog posts, videos, and supporting customers in perpetuity. In fact, implementing an API is of miniscule cost compared to all the other downstream activities. - -> For this reason, it is much better to ship with fewer features and only add new features over time as required by customers. - -Focusing on hero scenarios reduces development, support, and maintenance costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A telltale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. - -:white_check_mark: **DO** define "hero scenarios" first including abstractions, naming, relationships, and then define the API describing the operations required - -:white_check_mark: **DO** provide example code demonstrating the "Hero Scenarios" - -:no_entry: **DO NOT** proactively add APIs for speculative features customers might want - -### Start with your API Definition -Understanding how your service is used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It reflects the abstractions & naming decisions and makes it easy for developers to implement the hero scenarios. - -:white_check_mark: **DO** provide an [OpenAPI Definition][OpenAPI Definition] (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. - -### Use Previews to Iterate - Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. - -:ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. - -:ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. - -:ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. - -:ballot_box_with_check: **YOU SHOULD** consider doing a *code with* exercise in which you actively develop with the customer, observing and learning from their API usage. - -:ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. - -### Avoid Surprises -A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. - -:ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint __SHOULD__ work with a single type to avoid problems during SDK creation. - -:ballot_box_with_check: **YOU SHOULD** make [Collections](#Collections) easy to work with. Collections are a common source of review comments. It is important to handle them in a consistent manner within your service. - -:ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. - -:ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. - -### Design for Change Resiliency -As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. - -:ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. - -:ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. - -:ballot_box_with_check: **YOU SHOULD** use wider data types (e.g. 64-bit vs. 32-bit) as they are more future-proof. For example, JavaScript can only support integers up to 253, so relying on the full width of a 64-bit integer should be avoided. - ## Building Blocks: HTTP, REST, & JSON The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet; namely HTTP, REST, and JSON. This section provides you with a general understanding of how these technologies should be applied when creating your service. @@ -130,7 +44,7 @@ Azure services must adhere to the HTTP specification, [RFC7231](https://tools.ie A Uniform Resource Locator (URL) is how developers access the resources of your service. Ultimately, URLs are how developers form a cognitive model of your service's resources. -:white_check_mark: **DO** use this URL pattern: +> :white_check_mark: **DO** use this URL pattern: ```text https://.///// ``` @@ -145,24 +59,27 @@ Where: | resource‑collection | Name of the collection, unabbreviated, pluralized | resource‑id | Value of the unique id property. This MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. -:white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. +> :white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. +> +> :white_check_mark: **DO** return '414-URI Too Long' if a URL exceeds 2083 characters +> +> :white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request __MUST__ fail with the appropriate HTTP return code. -:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) +> :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) +> +> :ballot_box_with_check: **YOU SHOULD** limit your URL's characters to `0-9 A-Z a-z - . _ ~` -:ballot_box_with_check: **YOU SHOULD** limit your URL's characters to `0-9 A-Z a-z - . _ ~` +> :heavy_check_mark: **YOU MAY** use these other characters in the URL but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` -:heavy_check_mark: **YOU MAY** use these other characters in the URL but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` -:white_check_mark: **DO** return '414-URI Too Long' if a URL exceeds 2083 characters -:white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request __MUST__ fail with the appropriate HTTP return code. -> Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' +Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' -:white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a response JSON body +> :white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a response JSON body #### Direct Endpoint URLs -:heavy_check_mark: **YOU MAY** support a direct endpoint URL for performance/routing: +> :heavy_check_mark: **YOU MAY** support a direct endpoint URL for performance/routing: ```text https://-../... ``` @@ -172,9 +89,9 @@ Examples: - Response header ([RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4)): `content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2` - GUID format: `https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2` -:white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for `` or always a single verified domain. +> :white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for `` or always a single verified domain. -:heavy_check_mark: **YOU MAY** use URLs as values +> :heavy_check_mark: **YOU MAY** use URLs as values ``` https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy ``` @@ -184,17 +101,15 @@ The HTTP Request / Response pattern dictates how your API behaves. For example: Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, all service operations (including POST) must be idempotent. Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. ---- -> Exactly Once Behavior = Client Retries & Service Idempotency ---- - -:white_check_mark: **DO** ensure that __all__ HTTP methods are idempotent. - -:ballot_box_with_check: **YOU SHOULD** use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent. - -:heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response __must__ return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#Repeatability-of-requests)). +#### Exactly Once Behavior = Client Retries & Service Idempotency +> :white_check_mark: **DO** ensure that __all__ HTTP methods are idempotent. +> +> :ballot_box_with_check: **YOU SHOULD** use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent. +> +> :heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response __must__ return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#Repeatability-of-requests)). -:white_check_mark: **DO** adhere to the return codes in the following table when the method is successful: +##### HTTP Return Codes +> :white_check_mark: **DO** adhere to the return codes in the following table when the method is successful: Method | Description | Response Status Code -------|-------------|--------------------- @@ -205,20 +120,20 @@ GET | Read (i.e. list) a resource collection | 200-OK GET | Read the resource | 200-OK DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found -:white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase - -:white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a ```200-OK``` or ```201-Created```. - -:white_check_mark: **DO** return a ```204-No Content``` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) - -:white_check_mark: **DO** support caching and optimistic concurrency by honoring the the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers and by returning the etag and last-modified response headers +> :white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase +> +> :white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a ```200-OK``` or ```201-Created```. +> +> :white_check_mark: **DO** return a ```204-No Content``` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) +> +> :white_check_mark: **DO** support caching and optimistic concurrency by honoring the the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers and by returning the etag and last-modified response headers ### HTTP Query Parameters and Header Values Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. -:white_check_mark: **DO** validate all query parameter and request header values and fail the operation with ```400-Bad Request``` if any value fails validation. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. - -:white_check_mark: **DO** use the following table when translating strings: +> :white_check_mark: **DO** validate all query parameter and request header values and fail the operation with ```400-Bad Request``` if any value fails validation. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. +> +> :white_check_mark: **DO** use the following table when translating strings: Data type | Document that string must be --------- | ------- @@ -231,7 +146,6 @@ Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231](https://datatracker Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) Byte array | Base-64 encoded, max length -TODO: Expand the explanation for numbers. The table below lists the headers most used by Azure services: @@ -255,19 +169,19 @@ last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT *x-ms-error-code* | Response | see [Handling Errors](#Handling-Errors) retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)) -:white_check_mark: **DO** support all headers shown in *italics* - -:white_check_mark: **DO** specify headers using kebab-casing - -:white_check_mark: **DO** compare request header names using case-insensitivity - -:white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it - -:white_check_mark: **DO** accept and return date values in headers using the HTTP Date format as defined in [RFC 7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT" - -:no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated - -:no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. +> :white_check_mark: **DO** support all headers shown in *italics* +> +> :white_check_mark: **DO** specify headers using kebab-casing +> +> :white_check_mark: **DO** compare request header names using case-insensitivity +> +> :white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it +> +> :white_check_mark: **DO** accept and return date values in headers using the HTTP Date format as defined in [RFC 7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT" +> +> :no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated +> +> :no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. #### Additional References * [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) @@ -276,57 +190,58 @@ retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://da ### REpresentational State Transfer (REST) REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. -TODO: Add link to behavior section When designing your service, it is important to optimize for the developer using your API. -:white_check_mark: **DO** focus heavily on clear & consistent naming - -:white_check_mark: **DO** ensure your resource paths make sense - -:white_check_mark: **DO** simplify operations with few required query parameters & JSON fields - -:white_check_mark: **DO** establish clear contracts for string values - -:white_check_mark: **DO** use proper response codes/bodies so customer can diagnose their own problems and fix them without contacting Azure support or the service team +> :white_check_mark: **DO** focus heavily on clear & consistent naming +> +> :white_check_mark: **DO** ensure your resource paths make sense +> +> :white_check_mark: **DO** simplify operations with few required query parameters & JSON fields +> +> :white_check_mark: **DO** establish clear contracts for string values +> +> :white_check_mark: **DO** use proper response codes/bodies so customer can diagnose their own problems and fix them without contacting Azure support or the service team #### JSON Resource Schema & Field Mutability -:white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH response, GET response, and POST request/response on a given URL path. The PATCH request schema should contain all the same fields with no required fields. This allows one SDK type for input/output operations and enables the response to be passed back in a request. +> :white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH response, GET response, and POST request/response on a given URL path. The PATCH request schema should contain all the same fields with no required fields. This allows one SDK type for input/output operations and enables the response to be passed back in a request. +> +> :white_check_mark: **DO** think about your resource's fields and how they are used: -:white_check_mark: **DO** think about your resource's fields and how they are used: Field Mutability | Service Request's behavior for this field -----------------| ----------------------------------------- **Create** | Service honors field only when creating a resource. Minimize create-only fields so customers don't have to delete & re-create the resource. **Update** | Service honors field when creating or updating a resource **Read** | Service returns this field in a response. If the client passed a read-only field, the service __must__ fail the request unless the passed-in value matches the resource's current value -> In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically __not__ become a nullable filed in a SDK's data structure. THis allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. - -:white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. - -:white_check_mark: **DO** treat JSON field names with case-sensitivity. - -:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions but avoid if at all possible. - -:white_check_mark: **DO** use GET for resource retrieval and return JSON in the response body - -:white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. +In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically __not__ become a nullable filed in a SDK's data structure. THis allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. -:white_check_mark: **DO** use PUT with JSON for wholesale create/update operations. -> NOTE: If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT). - -:white_check_mark: **DO** use DELETE to remove a resource. - -:white_check_mark: **DO** fail an operation with ```400-Bad Request``` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. - -:no_entry: **DO NOT** return secret fields via GET. For example, do not return ```administratorPassword``` in JSON. - -:heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. +> :white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. +> +> :white_check_mark: **DO** treat JSON field names with case-sensitivity. +> +> :white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions but avoid if at all possible. +> +> :white_check_mark: **DO** use GET for resource retrieval and return JSON in the response body +> +> :white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. +> +> :white_check_mark: **DO** use PUT with JSON for wholesale create/update operations. *NOTE: If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT).* +> +> :white_check_mark: **DO** use DELETE to remove a resource. +> +> :white_check_mark: **DO** fail an operation with ```400-Bad Request``` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. -:no_entry: **DO NOT** add fields to the JSON if the value is easily computable from other fields to avoid bloating the body. +> :heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. + +> :no_entry: **DO NOT** return secret fields via GET. For example, do not return ```administratorPassword``` in JSON. +> +> :no_entry: **DO NOT** add fields to the JSON if the value is easily computable from other fields to avoid bloating the body. -:white_check_mark: **DO** follow the processing below to create/update/replace a resource: +##### Create / Update / Replease Processing Rules + +> :white_check_mark: **DO** follow the processing below to create/update/replace a resource: When using this method | if this condition happens | use this response code ---------------------- | ------------------------- | ---------------------- @@ -346,14 +261,16 @@ There are 2 kinds of errors: - An error where you expect customer code to gracefully recover at runtime - An error indicating a bug in customer code that is unlikely to be recoverable at runtime; the customer must just fix their code -:white_check_mark: **DO** return error an ```x-ms-error-code``` response header with a string value indicating what went wrong. -> NOTE: String values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future. - -:white_check_mark: **DO** carefully craft ```x-ms-error-code``` string values for errors that are recoverable at runtime. - -:heavy_check_mark: **YOU MAY** group common customer code errors into a few ```x-ms-error-code``` string values. - -:white_check_mark: **DO** provide a response body as follows: +> :white_check_mark: **DO** return error an ```x-ms-error-code``` response header with a string value indicating what went wrong. +> *NOTE: String values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future.* +> +> :white_check_mark: **DO** carefully craft ```x-ms-error-code``` string values for errors that are recoverable at runtime. +> +> :white_check_mark: **DO** ensure that the top-level ```code``` field's value is identical to the ```x-ms-error-code``` header's value (see example body below). +> +> :white_check_mark: **DO** document the service's error code strings; they are part of the API contract. +> +> :white_check_mark: **DO** provide a response body as follows (example): ```json { "error": { @@ -368,11 +285,10 @@ There are 2 kinds of errors: } ``` -:white_check_mark: **DO** ensure that the top-level ```code``` field's value is identical to the ```x-ms-error-code``` header's value. - -:heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are __not__ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. +> :heavy_check_mark: **YOU MAY** group common customer code errors into a few ```x-ms-error-code``` string values. +> +> :heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are __not__ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. -:white_check_mark: **DO** document the service's error code strings; they are part of the API contract. ### JSON Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: @@ -383,25 +299,25 @@ Services, and the clients that access them, may be written in multiple languages Number | Signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) String | Used for everything else -:white_check_mark: **DO** use integers within the acceptable range of JSON number. - -:white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. - -:white_check_mark: **DO** use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC3339 for date/time. - -:white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable" across multiple programming languages. - -:white_check_mark: **DO** use [RFC3339] for date/time. - -:white_check_mark: **DO** use [RFC4122] for UUIDs. - -:heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. +> :white_check_mark: **DO** use integers within the acceptable range of JSON number. +> +> :white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. +> +> :white_check_mark: **DO** use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC3339 for date/time. +> +> :white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable" across multiple programming languages. +> +> :white_check_mark: **DO** use [RFC3339] for date/time. +> +> :white_check_mark: **DO** use [RFC4122] for UUIDs. -:warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. +> :heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. +> +> :heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. -:heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. +> :ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays whenever possible. -:ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays whenever possible. +> :warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. #### Enums & SDKs (Client libraries) It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI definition as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation. @@ -409,19 +325,22 @@ It is common for strings to have an explicit set of values. These are often refl However, it is not uncommon for the set of values to grow over the life of a service. For this reason, Microsoft's tooling uses the concept of an "extensible enum," which indicates that the set of values should be treated as only a *partial* list. This indicates to client libraries and customers that values of the enumeration field should be effectively treated as strings and that undocumented value may returned in the future. This enables the set of values to grow over time while ensuring stability in client libraries and customer code. -:white_check_mark: **DO** use "extensible enums" - -:ballot_box_with_check: **DO** document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow. - -:no_entry: **DO NOT** send "enum integers" over the wire. - -:no_entry: **DO NOT** remove values from your enumeration list as this breaks customer code. +> :white_check_mark: **DO** use "extensible enums" +> +> :white_check_mark: **DO** document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow. + +> :no_entry: **DO NOT** send "enum integers" over the wire. +> +> :no_entry: **DO NOT** remove values from your enumeration list as this breaks customer code. #### Polymorphic types -:warning: **YOU SHOULD NOT** use polymorphic JSON types because they greatly complicate the customer code due to runtime dynamic casts and the introduction of new types in the future. If you can't avoid them, then follow these guidelines: +> :warning: **YOU SHOULD NOT** use polymorphic JSON types because they greatly complicate the customer code due to runtime dynamic casts and the introduction of new types in the future. + +If you can't avoid them, then follow the guideline below. -:white_check_mark: **DO** define a ```kind``` field indicating the kind of the resource and include any kind-specific fields in the body. Below is an example of JSON for a Rectangle and Circle: +> :white_check_mark: **DO** define a ```kind``` field indicating the kind of the resource and include any kind-specific fields in the body. +Below is an example of JSON for a Rectangle and Circle: **Rectangle** ```json { @@ -454,69 +373,67 @@ This indicates to client libraries and customers that values of the enumeration } } ``` - -> Both Rectangle and Circle has common fields: ```kind```, ```fillColor```, ```lineColor```, and ```subscription```. A Rectangle also has ```x```, ```y```, ```width```, and ```length``` while a Circle has ```x```, ```y```, and ```radius```. The ```subscription``` is a nested polymorphic type. A ```free``` subscription has no additional fields and a ```paid``` subscription has ```expiration``` and ```invoice``` fields. +Both Rectangle and Circle has common fields: ```kind```, ```fillColor```, ```lineColor```, and ```subscription```. A Rectangle also has ```x```, ```y```, ```width```, and ```length``` while a Circle has ```x```, ```y```, and ```radius```. The ```subscription``` is a nested polymorphic type. A ```free``` subscription has no additional fields and a ```paid``` subscription has ```expiration``` and ```invoice``` fields. ## Common API Patterns ### Performing an Action The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image, sending an SMS message. -:white_check_mark: **DO** pattern your URL like this to perform an action on a resource -> **URL Pattern** -> -> ```https://...///:?``` -> -> **SMS Example** -> -> ```https://.../users/Bob/:send-sms?Text="Hello"``` +> :white_check_mark: **DO** pattern your URL like this to perform an action on a resource +**URL Pattern** + ```https://...///:?``` -> **Equivalent to (in C#)** -> -> ```users["Bob"].SendSms("Hello")``` +**SMS Example** -:white_check_mark: **DO** use a POST operation for any action on a resource. + ```https://.../users/Bob/:send-sms?Text="Hello"``` -:ballot_box_with_check: **YOU SHOULD** use a verb to name your action. +**Equivalent to (in C#)** +```users["Bob"].SendSms("Hello")``` -:white_check_mark: **DO** support the Repeatability-Request-ID & Repeatability-First-Sent request headers if the action needs to be idempotent if retries occur. +> :white_check_mark: **DO** use a POST operation for any action on a resource. +> +> :white_check_mark: **DO** support the Repeatability-Request-ID & Repeatability-First-Sent request headers if the action needs to be idempotent if retries occur. -### Collections -:heavy_check_mark: **YOU MAY** expose an operation that lists your resources by supporting a GET method with a URL to a resource-collection (as opposed to a resource-id). +> :ballot_box_with_check: **YOU SHOULD** use a verb to name your action. -:ballot_box_with_check: **YOU SHOULD** support paging today if there is ever a chance in the future that the number of items can grow to be very large. -> NOTE: It is a breaking change to add paging in the future -:white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources. +### Collections +> :white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources. -> **Example Response Body** -> ``` -> { -> "value": [ -> { "id": "Item 01", "etag": "0xabc", "price": 99.95, "sizes": null }, -> { … }, -> { … }, -> { "id": "Item 99", "etag": "0xdef", "price": 59.99, "sizes": null } -> ], -> "nextLink": "{opaqueUrl}" -> } ->``` +> :ballot_box_with_check: **YOU SHOULD** support paging today if there is ever a chance in the future that the number of items can grow to be very large. +> NOTE: It is a breaking change to add paging in the future -:ballot_box_with_check: **YOU SHOULD** use ```value``` as the name of the top-level array field unless a more appropriate name is available. +> :heavy_check_mark: **YOU MAY** expose an operation that lists your resources by supporting a GET method with a URL to a resource-collection (as opposed to a resource-id). -:white_check_mark: **DO** include the id field and etag field (if supported) for each item as this allows the customer to modify the item in a future operation. +**Example Response Body** +``` +{ + "value": [ + { "id": "Item 01", "etag": "0xabc", "price": 99.95, "sizes": null }, + { … }, + { … }, + { "id": "Item 99", "etag": "0xdef", "price": 59.99, "sizes": null } + ], + "nextLink": "{opaqueUrl}" + } +``` -:white_check_mark: **DO** return a ```nextLink``` field with a URL that the client can GET in order to retrieve the next page of the collection. -:no_entry: **DO NOT** return the ```nextLink``` field at all when returning the last page of the collection. +> :white_check_mark: **DO** include the id field and etag field (if supported) for each item as this allows the customer to modify the item in a future operation. +> +> :white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). +> +> :white_check_mark: **DO** return a ```nextLink``` field with a URL that the client can GET in order to retrieve the next page of the collection. -:no_entry: **DO NOT** ever return a ```nextLink``` field with a value of null. +> :ballot_box_with_check: **YOU SHOULD** use ```value``` as the name of the top-level array field unless a more appropriate name is available. -:white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). +> :no_entry: **DO NOT** return the ```nextLink``` field at all when returning the last page of the collection. +> :no_entry: **DO NOT** ever return a ```nextLink``` field with a value of null. #### Query options -:heavy_check_mark: **YOU MAY** support the following query parameters allowing customers to control the list operation: +> :heavy_check_mark: **YOU MAY** support the following query parameters allowing customers to control the list operation: Parameter name | Type | Description -------------- | ---- | ----------- _filter_ | string | an expression on the resource type that selects the resources to be returned @@ -527,23 +444,23 @@ _maxpagesize_ | integer | the maximum number of resources to include _select_ | string array | a list of field names to be returned for each resource _expand_ | string array | a list of the related resources to be included in line with each resource -:white_check_mark: **DO** return an error if the client specifies any parameter not supported by the service. - -:white_check_mark: **DO** treat these query parameter names as case-sensitive. - -:no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). - -:white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. +> :white_check_mark: **DO** return an error if the client specifies any parameter not supported by the service. +> +> :white_check_mark: **DO** treat these query parameter names as case-sensitive. +> +> :white_check_mark: **DO** apply _select_ or _expand_ options after applying all the query options in the table above. +> +> :white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. -:white_check_mark: **DO** apply _select_ or _expand_ options after applying all the query options in the table above. +> :no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). #### filter -:heavy_check_mark: **YOU MAY** support filtering of the results of a list operation with the _filter_ query parameter. +> :heavy_check_mark: **YOU MAY** support filtering of the results of a list operation with the _filter_ query parameter. The value of the _filter_ option is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response. -:white_check_mark: **DO** omit all resources from the collection for which the _filter_ expression evaluates to false or to null, or references properties that are unavailable due to permissions. +> :white_check_mark: **DO** omit all resources from the collection for which the _filter_ expression evaluates to false or to null, or references properties that are unavailable due to permissions. Example: return all Products whose Price is less than $10.00 @@ -553,7 +470,7 @@ GET https://api.contoso.com/products?filter=price lt 10.00 ##### filter operators -:heavy_check_mark: **YOU MAY** support the following operators in _filter_ expressions: +> :heavy_check_mark: **YOU MAY** support the following operators in _filter_ expressions: Operator | Description | Example -------------------- | --------------------- | ----------------------------------------------------- __Comparison Operators__ | | @@ -570,9 +487,9 @@ not | Logical negation | not price le 3.5 __Grouping Operators__ | | ( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 -:white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if a client includes an operator in a _filter_ expression that is not supported by the operation. - -:white_check_mark: **DO** use the following operator precedence for supported operators when evaluating _filter_ expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: +> :white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if a client includes an operator in a _filter_ expression that is not supported by the operation. +> +> :white_check_mark: **DO** use the following operator precedence for supported operators when evaluating _filter_ expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: | Group | Operator | Description | ----------------|----------|------------ @@ -622,23 +539,23 @@ GET https://api.contoso.com/products?filter=(name eq 'Milk' or name eq 'Eggs') a #### orderby -:heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the _orderby_ query parameter. -> NOTE: It is unusual for a service to support __orderby__ because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results. +> :heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the _orderby_ query parameter. +*NOTE: It is unusual for a service to support __orderby__ because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results.* The value of the _orderby_ parameter is a comma-separated list of expressions used to sort the items. A special case of such an expression is a property path terminating on a primitive property. Each expression in the _orderby_ parameter value may include the suffix "asc" for ascending or "desc" for descending, separated from the expression by one or more spaces. -:white_check_mark: **DO** sort the collection in ascending order on an expression if "asc" or "desc" is not specified. - -:white_check_mark: **DO** sort NULL values as "less than" non-NULL values. - -:white_check_mark: **DO** sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on. - -:white_check_mark: **DO** use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically. - -:white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if the client requests sorting by a field that is not supported by the operation. +> :white_check_mark: **DO** sort the collection in ascending order on an expression if "asc" or "desc" is not specified. +> +> :white_check_mark: **DO** sort NULL values as "less than" non-NULL values. +> +> :white_check_mark: **DO** sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on. +> +> :white_check_mark: **DO** use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically. +> +> :white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if the client requests sorting by a field that is not supported by the operation. For example, to return all people sorted by name in ascending order: ```http @@ -658,63 +575,54 @@ will return all people whose name is David sorted in ascending order by hireDate ##### Considerations for sorting with pagination -:white_check_mark: **DO** use the same filtering options and sort order for all pages of a paginated list operation response. +> :white_check_mark: **DO** use the same filtering options and sort order for all pages of a paginated list operation response. ##### skip +> :white_check_mark: **DO** define the _skip_ parameter as an integer with a default and minimum value of 0. -:heavy_check_mark: **YOU MAY** allow clients to pass the _skip_ query parameter to specify an offset into collection of the first resource to be returned. - -:white_check_mark: **DO** define the _skip_ parameter as an integer with a default and minimum value of 0. - +> :heavy_check_mark: **YOU MAY** allow clients to pass the _skip_ query parameter to specify an offset into collection of the first resource to be returned. ##### top -:heavy_check_mark: **YOU MAY** allow clients to pass the _top_ query parameter to specify the maximum number of resources to return from the collection. - -:white_check_mark: **DO** define the _top_ parameter as an integer with a minimum value of 1. If not specified, __top__ has a default value of infinity. +> :heavy_check_mark: **YOU MAY** allow clients to pass the _top_ query parameter to specify the maximum number of resources to return from the collection. -:white_check_mark: **DO** return the collection's _top_ number of resources (if available), starting from _skip_. +If supporting _top_: +> :white_check_mark: **DO** define the _top_ parameter as an integer with a minimum value of 1. If not specified, __top__ has a default value of infinity. +> +> :white_check_mark: **DO** return the collection's _top_ number of resources (if available), starting from _skip_. ##### maxpagesize -:heavy_check_mark: **YOU MAY** allow clients to pass the _maxpagesize_ query parameter to specify the maximum number of resources to include in a single page response. +> :heavy_check_mark: **YOU MAY** allow clients to pass the _maxpagesize_ query parameter to specify the maximum number of resources to include in a single page response. -:white_check_mark: **DO** define the _maxpagesize_ parameter as an optional integer with a default value appropriate for the collection. +If supporting _maxpagesize_ -:white_check_mark: **DO** make clear in documentation of the _maxpagesize_ parameter that the operation may choose to return fewer resources than the value specified. +> :white_check_mark: **DO** define the _maxpagesize_ parameter as an optional integer with a default value appropriate for the collection. +> +> :white_check_mark: **DO** make clear in documentation of the _maxpagesize_ parameter that the operation may choose to return fewer resources than the value specified. ### API Versioning Azure services need to change over time. However, when changing a service, there are 2 requirements: 1. Already-running customer workloads must never break due to a service change - 2. Customers can adopt a new service version without requiring any code changes - - Of course, the customer must modify code to leverage any new service features - -:ballot_box_with_check: **DO** review any API changes with the Azure API Stewardship Board + 2. Customers can adopt a new service version without requiring any code changes (Of course, the customer must modify code to leverage any new service features.) -:white_check_mark: **DO** use an 'api-version' query parameter with a date value +> :ballot_box_with_check: **DO** review any API changes with the Azure API Stewardship Board +> +> :white_check_mark: **DO** use an 'api-version' query parameter with a date value ```text PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 ``` -:white_check_mark: **DO** use a later date for each new version - -:white_check_mark: **DO** suffix the date with '-preview' for a preview API -```text -PUT https://service.azure.com/users/Jeff?api-version=2021-06-04-preview -``` -> The only difference between a private and public preview is whether you publicly document the API; there is no impact to the 'api-version' value - -:no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview 'api-version' is '2021-06-04-preview', the GA version of the API must be a date later than 2021-06-04 - -:white_check_mark: **DO** use a later date for each new preview version +> :white_check_mark: **DO** use a later date for each new preview version > When releasing a new preview, the service team may completely retire any previous preview versions after giving customers at least 90 days to upgrade their code +> :white_check_mark: **DO** use a later date for successive preview versions. -:white_check_mark: **DO** use a later date for successive preview versions. - -:no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. - -:no_entry: **DO NOT** introduce any breaking changes into service. -> NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy/) has tables (section 5) describing what kinds of changes are considered breaking. If a new service version must break customers (due to security/compliance/etc.), contact the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) as soon as possible. +> :no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview 'api-version' is '2021-06-04-preview', the GA version of the API must be a date later than 2021-06-04 +> +> :no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. +> +> :no_entry: **DO NOT** introduce any breaking changes into service. +*NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy/) has tables (section 5) describing what kinds of changes are considered breaking. If a new service version must break customers (due to security/compliance/etc.), contact the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) as soon as possible.* #### Use Extensible Enums @@ -737,7 +645,7 @@ While removing a value from an enum is a breaking change, adding value to an enu } ``` -:ballot_box_with_check: **DO** model an ```enum``` as a string unless you are positive that the symbol set will **NEVER** change over time. +> :white_check_mark: **DO** model an ```enum``` as a string unless you are positive that the symbol set will **NEVER** change over time. #### Version Discovery @@ -745,7 +653,7 @@ Simpler clients may be hardcoded to a single version of a service. Since Azure s API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. -:white_check_mark: **DO** support API version discovery, including +> :white_check_mark: **DO** support API version discovery, including 1. Support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service @@ -755,7 +663,7 @@ API version discovery is needed when either a given hosted service may expose a 4. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. -:ballot_box_with_check: **YOU SHOULD** support the following for version discovery: +> :ballot_box_with_check: **YOU SHOULD** support the following for version discovery: 1. In addition to the functionality described here, services should support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. @@ -785,7 +693,7 @@ Clients that use version discovery are expected to cache version information. Si The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. -A service **SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). +> :ballot_box_with_check: **YOU SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). - The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. - A service advertises support for repeatability requests by adding the `Repeatability-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. @@ -798,35 +706,33 @@ A service **SHOULD** support repeatable requests according as defined in [OASIS The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. -:white_check_mark: **DO** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. - -:white_check_mark: **DO** return the same value for **both** headers. - -:white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. +> :white_check_mark: **DO** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. +> +> :white_check_mark: **DO** return the same value for **both** headers. +> +> :white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. ### Bring your own Storage When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS will provide the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders, nor do they have extensions. -:white_check_mark: **DO** use Azure Bring Your Own Storage. - -:no_entry: **DO NOT** require a fresh container per operation +> :white_check_mark: **DO** use Azure Bring Your Own Storage. +> +> :white_check_mark: **DO** use a blob prefix -:white_check_mark: **DO** use a blob prefix instead +> :no_entry: **DO NOT** require a fresh container per operation #### Authentication How you secure and protect the data and files that your service uses will not only affect how consumable your API is, but also, how quickly you can evolve and adapt it. Implementing Role Based Access Control [RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) is the recommended approach. It is important to recognize that any roles defined in RBAC essentially become part of your API contract. For example, changing a role's permissions, e.g. restricting access, could effectively cause existing clients to break, as they may no longer have access to necessary resources. -:white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions. - -:white_check_mark: **DO** Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. +> :white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions. +> +> :white_check_mark: **DO** Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. ##### Handling 'downstream' errors It is not uncommon to rely on other services, e.g. storage, when implementing your service. Inevitably, the services you depend on will fail. In these situations, you can include the downstream error code and text in the inner-error of the response body. This provides a consistent pattern for handling errors in the services you depend upon. -:white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. - - TODO: There are some security considerations here, e.g. returning the endpoint/url with a status code that exists/doesn't exist. Johan ) +> :white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. #### Working with files Generally speaking, there are two patterns that you will encounter when working with files; single file access, and file collections. @@ -834,22 +740,22 @@ Generally speaking, there are two patterns that you will encounter when working ##### Single file access Desiging an API for accessing a single file, depending on your scenario, is relatively straight forward. -:heavy_check_mark: **YOU MAY** use a Shared Access Signature [SAS](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. - -:ballot_box_with_check: **YOU SHOULD** if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text. +> :heavy_check_mark: **YOU MAY** use a Shared Access Signature [SAS](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. -:ballot_box_with_check: **YOU SHOULD** support managed identity using Azure Storage by default (if using Azure services). +> :ballot_box_with_check: **YOU SHOULD** if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text. +> +> :ballot_box_with_check: **YOU SHOULD** support managed identity using Azure Storage by default (if using Azure services). ###### File Versioning Depending on your requirements, there are scenarios where users of your service will require a specific version of a file. For example, you may need to keep track of configuration changes over time to be able to rollback to a previous state. In these scenarios, you will need to provide a mechanism for accessing a specific version. -:white_check_mark: **DO** Enable the customer to provide an ETag to specify a specific version of a file. +> :white_check_mark: **DO** Enable the customer to provide an ETag to specify a specific version of a file. ##### File Collections When your users need to work with multiple files, for example a document translation service, it will be important to provide them access to the collection, and its contents, in a consistent manner. Because there is no industry standard for working with with containers, these guidelines will recommend that you leverage Azure Storage. Following the guidelines above, you also want to ensure that you don't expose file system constructs, e.g. folders, and instead use storage constructs, e.g. blob prefixes. -:white_check_mark: **DO** When using a Shared Access Signature (SAS), ensure this is assigned to the container and that the permissions apply to the content as well. - -:white_check_mark: **DO** When using managed identity, ensure the customer has given the proper permissions to access the file container to the service. +> :white_check_mark: **DO** When using a Shared Access Signature (SAS), ensure this is assigned to the container and that the permissions apply to the content as well. +> +> :white_check_mark: **DO** When using managed identity, ensure the customer has given the proper permissions to access the file container to the service. A common pattern when working with multiple files is for your service to receive requests that contain the location(s) of files to process, e.g. "input" and a location(s) to place the any files that result from processing, e.g. "output." (Note: the terms "input" and "output" are just examples and terms more relevant to the service domain are more appropriate.) @@ -871,23 +777,19 @@ Note: How the service gets the request body is outside the purview of these guid Depending on the requirements of the service, there can be any number of "input" and "output" sections, including none. However, for each of the "input" sections the following apply: -:white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter." - -:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```listing``` and ```read``` permissions. +> :white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter." +> +> :white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```listing``` and ```read``` permissions. For each of the "output" sections the following apply: -:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```write``` permissions - - - TODO: Add the proper links for 'additional references' ) - +> :white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```write``` permissions ### Conditional Requests When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an ```ETag``` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. An ```ETag``` is always set by the service and will enable you to *conditionally* control how your service responds to requests, enabling you to provide predictable updates and more efficient access. -:ballot_box_with_check: **YOU SHOULD** return an ```ETag``` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). - -:ballot_box_with_check: **YOU SHOULD** use ```etags``` consistently across your API, i.e. if you use an ```ETag```, accept it on all other operations. +> :ballot_box_with_check: **YOU SHOULD** return an ```ETag``` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). +> +> :ballot_box_with_check: **YOU SHOULD** use ```etags``` consistently across your API, i.e. if you use an ```ETag```, accept it on all other operations. You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). @@ -896,9 +798,10 @@ One of the more common uses for ```ETag``` headers is cache control, also referr Implementing this strategy is relatively straightforward. First, you will return an ```ETag``` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your ```ETag```. In these scenarios, when a request is made by the client an ```ETag``` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The ```ETag``` value can then be sent in subsequent requests as part of the ```if-none-match``` header. This tells the service to compare the ```ETag``` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated ```ETag``` value in the header. -:ballot_box_with_check: **YOU SHOULD** implement conditional read strategies +> :ballot_box_with_check: **YOU SHOULD** implement conditional read strategies -:white_check_mark: **DO** adhere to the following table for guidance: +When supporting conditional read strategies: +> :white_check_mark: **DO** adhere to the following table for guidance: | GET Request | Return code | Response | |:------------|:------------|:--------------------------------------------| @@ -910,9 +813,10 @@ For more control over caching, please refer to the ```cache-control``` [HTTP hea #### Optimistic Concurrency An ```ETag``` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of ```ETags``` and the [HTTP Request / Response Pattern](#http-request--response-pattern). -:warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. +> :warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. -:white_check_mark: **DO** adhere to the following table for guidance: +When supporting optimistic concurrency: +> :white_check_mark: **DO** adhere to the following table for guidance: | Operation | Header | Value | etag check | Return code | Response | |:------------|:--------------|:------|:-----------|:------------|----------------| @@ -926,19 +830,19 @@ An ```ETag``` should also be used to reflect the create, update, and delete poli #### Computing ETags The strategy that you use to compute the ```ETag``` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the ```ETag```. Another common strategy for determining the value of an ```ETag``` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. -:heavy_check_mark: **YOU MAY** use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. +> :ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. -:ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. - -:heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. +> :heavy_check_mark: **YOU MAY** use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. +> +> :heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. ### Distributed Tracing & Telemetry Azure SDK client guidelines specify that client libraries must send telemetry data through the ```User-Agent``` header, ```X-MS-UserAgent``` header, and Open Telemetry. Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. -:white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. +> :white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. -:no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. +> :no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. #### Additional References * [Azure SDK client guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) * [Azure SDK User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) @@ -948,12 +852,4 @@ Client libraries are required to send telemetry and distributed tracing informat ## Final thoughts These guidelines describe the upfront design considerations, technology building blocks, and common patterns that Azure teams encounter when building an API for their service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. -The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. -### Typical Review Session -When engaging with the API REST Stewardship board, your working sessions will generally focus on three areas: -* Correctness - Your service should leverage the proper HTTP verbs, return codes, and respect the core constructs of a REST API, e.g. idempotency, that are standard throughout the industry. -* Consistency - Your services should look and behave as though they are natural part of the Azure platform. -* Well formed - Do your services adhere to REST and Azure standards, e.g. proper return codes, use of headers. -* Sustainable - Your APIs will grow and change over time and leveraging the common patterns described in this document will help you minimize your tech debt and move fast with confidence. - -It was once said that "all roads lead to Rome." For cloud services, the equivalent might be that "all 'roads' start with your API." That could not be more true than at Microsoft, where client libraries, documentation, and many other artifacts all originate from the fundamental way you choose to expose your service. With careful consideration at the outset of your development effort, the architectural stewardship of the API board, and the thoughtful application of these guidelines, you will be able to produce a consistent, well formed API that will delight our customers. \ No newline at end of file +The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. For more information on how to part with the Stewardship board, please refer to [Considerations for Service Design](.\ConsiderationsForServiceDesign.md). \ No newline at end of file From 6891fe3ba10a73e8a56c0dfd50a7181f6f4b18d7 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 2 Jul 2021 16:02:08 -0400 Subject: [PATCH 128/729] Formatting & typos --- azure/Guidelines.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 508de972..0048753c 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -103,9 +103,9 @@ Cloud applications embrace failure. Therefore, to enable customers to write faul #### Exactly Once Behavior = Client Retries & Service Idempotency > :white_check_mark: **DO** ensure that __all__ HTTP methods are idempotent. -> + > :ballot_box_with_check: **YOU SHOULD** use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent. -> + > :heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response __must__ return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#Repeatability-of-requests)). ##### HTTP Return Codes @@ -178,7 +178,7 @@ retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://da > :white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it > > :white_check_mark: **DO** accept and return date values in headers using the HTTP Date format as defined in [RFC 7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT" -> + > :no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated > > :no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. @@ -419,7 +419,6 @@ The REST specification is used to model the state of a resource, and is primaril } ``` - > :white_check_mark: **DO** include the id field and etag field (if supported) for each item as this allows the customer to modify the item in a future operation. > > :white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). @@ -429,13 +428,14 @@ The REST specification is used to model the state of a resource, and is primaril > :ballot_box_with_check: **YOU SHOULD** use ```value``` as the name of the top-level array field unless a more appropriate name is available. > :no_entry: **DO NOT** return the ```nextLink``` field at all when returning the last page of the collection. - +> > :no_entry: **DO NOT** ever return a ```nextLink``` field with a value of null. #### Query options > :heavy_check_mark: **YOU MAY** support the following query parameters allowing customers to control the list operation: + Parameter name | Type | Description --------------- | ---- | ----------- +------------------- | ---- | ----------- _filter_ | string | an expression on the resource type that selects the resources to be returned _orderby_ | string array | a list of expressions that specify the order of the returned resources _skip_ | integer | an offset into the collection of the first resource to be returned @@ -471,6 +471,7 @@ GET https://api.contoso.com/products?filter=price lt 10.00 ##### filter operators > :heavy_check_mark: **YOU MAY** support the following operators in _filter_ expressions: + Operator | Description | Example -------------------- | --------------------- | ----------------------------------------------------- __Comparison Operators__ | | @@ -606,13 +607,11 @@ Azure services need to change over time. However, when changing a service, there 1. Already-running customer workloads must never break due to a service change 2. Customers can adopt a new service version without requiring any code changes (Of course, the customer must modify code to leverage any new service features.) -> :ballot_box_with_check: **DO** review any API changes with the Azure API Stewardship Board +> :white_check_mark: **DO** review any API changes with the Azure API Stewardship Board > > :white_check_mark: **DO** use an 'api-version' query parameter with a date value -```text -PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 -``` - +> ```text PUT https://service.azure.com/users/Jeff?api-version=2021-06-04``` +> > :white_check_mark: **DO** use a later date for each new preview version > When releasing a new preview, the service team may completely retire any previous preview versions after giving customers at least 90 days to upgrade their code > :white_check_mark: **DO** use a later date for successive preview versions. From fae91656af7ff2ee76a85ccc76d7023116984595 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 2 Jul 2021 16:28:23 -0400 Subject: [PATCH 129/729] Added back history section. --- azure/Guidelines.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 0048753c..fad06aeb 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,4 +1,11 @@ -# Microsoft Azure HTTP/REST API Guidelines +# Microsoft Azure REST API Guidelines +## History + +| Date | Version | Notes | +| ----------- | ------- | --------------------------------------------------- | +| 2021-Jul-02 | vNext-RC1 | Updated Azure REST Guidelines. | +| 2020-Jul-31 | v3.2 | Added service advice for initial versions | +| 2020-Mar-31 | v3.1 | 1st public release of the Azure REST API Guidelines | ## Introduction From 81ce3dab4cb4402e3d87610e76d4c32daea20381 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 2 Jul 2021 14:17:03 -0700 Subject: [PATCH 130/729] Update azure/Guidelines.md Co-authored-by: Mike Kistler <85643503+mikekistler@users.noreply.github.com> --- azure/Guidelines.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index fad06aeb..2830bc0d 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -25,7 +25,7 @@ Technology and software is constantly changing and evolving, and as such, this i ### Prescriptive Guidance This document offers prescriptive guidance labeled as follows: -> :white_check_mark: **DO** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board[TODO: mail link? - not for public people] prior to implementation. +> :white_check_mark: **DO** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. > :ballot_box_with_check: **YOU SHOULD** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. @@ -68,7 +68,7 @@ Where: > :white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. > -> :white_check_mark: **DO** return '414-URI Too Long' if a URL exceeds 2083 characters +> :white_check_mark: **DO** return ```414-URI Too Long``` if a URL exceeds 2083 characters > > :white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request __MUST__ fail with the appropriate HTTP return code. @@ -82,7 +82,7 @@ Where: Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' -> :white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a response JSON body +> :white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a JSON response body #### Direct Endpoint URLs @@ -138,7 +138,7 @@ DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found ### HTTP Query Parameters and Header Values Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. -> :white_check_mark: **DO** validate all query parameter and request header values and fail the operation with ```400-Bad Request``` if any value fails validation. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. +> :white_check_mark: **DO** validate all query parameter and request header values and fail the operation with ```400-Bad Request``` if any value fails validation. Return an error response as described in the [Handling Errors](#Handling-errors) section indicating what is wrong so customer can diagnose the issue and fix it themselves. > > :white_check_mark: **DO** use the following table when translating strings: @@ -222,7 +222,7 @@ Field Mutability | Service Request's behavior for this field **Update** | Service honors field when creating or updating a resource **Read** | Service returns this field in a response. If the client passed a read-only field, the service __must__ fail the request unless the passed-in value matches the resource's current value -In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically __not__ become a nullable filed in a SDK's data structure. THis allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. +In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically __not__ become a nullable field in a SDK's data structure. This allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. > :white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. > @@ -332,7 +332,7 @@ It is common for strings to have an explicit set of values. These are often refl However, it is not uncommon for the set of values to grow over the life of a service. For this reason, Microsoft's tooling uses the concept of an "extensible enum," which indicates that the set of values should be treated as only a *partial* list. This indicates to client libraries and customers that values of the enumeration field should be effectively treated as strings and that undocumented value may returned in the future. This enables the set of values to grow over time while ensuring stability in client libraries and customer code. -> :white_check_mark: **DO** use "extensible enums" +> :ballot_box_with_check: **YOU SHOULD** use extensible enumerations unless you are positive that the symbol set will NEVER change over time. > > :white_check_mark: **DO** document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow. @@ -495,7 +495,7 @@ not | Logical negation | not price le 3.5 __Grouping Operators__ | | ( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 -> :white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if a client includes an operator in a _filter_ expression that is not supported by the operation. +> :white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](handling-errors) section if a client includes an operator in a _filter_ expression that is not supported by the operation. > > :white_check_mark: **DO** use the following operator precedence for supported operators when evaluating _filter_ expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: @@ -627,7 +627,7 @@ Azure services need to change over time. However, when changing a service, there > > :no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. > -> :no_entry: **DO NOT** introduce any breaking changes into service. +> :no_entry: **DO NOT** introduce any breaking changes into the service. *NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy/) has tables (section 5) describing what kinds of changes are considered breaking. If a new service version must break customers (due to security/compliance/etc.), contact the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) as soon as possible.* #### Use Extensible Enums @@ -651,7 +651,7 @@ While removing a value from an enum is a breaking change, adding value to an enu } ``` -> :white_check_mark: **DO** model an ```enum``` as a string unless you are positive that the symbol set will **NEVER** change over time. +> :ballot_box_with_check: **You SHOULD** use extensible enums unless you are positive that the symbol set will **NEVER** change over time. #### Version Discovery @@ -858,4 +858,4 @@ Client libraries are required to send telemetry and distributed tracing informat ## Final thoughts These guidelines describe the upfront design considerations, technology building blocks, and common patterns that Azure teams encounter when building an API for their service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. -The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. For more information on how to part with the Stewardship board, please refer to [Considerations for Service Design](.\ConsiderationsForServiceDesign.md). \ No newline at end of file +The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. For more information on how to part with the Stewardship board, please refer to [Considerations for Service Design](.\ConsiderationsForServiceDesign.md). From 595a9e2f787669888604176e15a980493509a918 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 7 Jul 2021 06:58:09 -0700 Subject: [PATCH 131/729] Apply suggestions from code review Co-authored-by: Mark Weitzel --- azure/Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 2830bc0d..84f363c2 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -830,8 +830,8 @@ When supporting optimistic concurrency: | PATCH / PUT | if-none-match | * | check for *any* version of the resource, if one is found, fail the operation | 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| | PATCH / PUT | if-match | value of etag | value of if-match equals the latest etag value on the server, confirming that the version of the resource is the most current | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | | PATCH / PUT | if-match | value of etag | value of if-match header DOES NOT equal the latest etag value on the server, indicating a change has ocurred since after the client fetched the resource| 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| -| DELETE | if-none-match | value of etag | value does NOT match the latest value on the server | 412 Preconditioned Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request. | -| DELETE | if-none-match | value of etag | value matches the latest value on the server | 200 OK or
204 No Content | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request. | +| DELETE | if-none-match | value of etag | value does NOT match the latest value on the server | 412 Preconditioned Failed | Response body SHOULD be empty.| +| DELETE | if-none-match | value of etag | value matches the latest value on the server | 204 No Content | Response body SHOULD be empty. | #### Computing ETags The strategy that you use to compute the ```ETag``` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the ```ETag```. Another common strategy for determining the value of an ```ETag``` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. From 4ccfed986026cf84a3468d6195789f59f4081eee Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 8 Jul 2021 17:27:31 -0700 Subject: [PATCH 132/729] Address PR review comments --- azure/ConsiderationsForServiceDesign.md | 16 ++++++++-------- azure/Guidelines.md | 11 ++++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 65aa4343..8746943a 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -14,7 +14,7 @@ Your goal is to create a developer friendly API where: ### Azure Management Plane vs Data Plane *Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review.* -A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful to both administrators and developers. In this case, the operation can appear in both APIs. Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on the conventions of the Azure ARM. +A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful to both administrators and developers. In this case, the operation can appear in both APIs. Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on [the conventions of the Azure ARM](https://github.com/Azure/azure-resource-manager-rpc). ### Start with the Developer Experience A great API starts with a well thought out and designed service. Your service should define simple/understandable abstractions with each given a clear name that you use consistently throughout your API and documentation. There must also be an unambiguous relationship between these abstractions. @@ -27,11 +27,7 @@ It is extremely difficult to create an elegant API that works well on top of a p - Initializing and reading from the data structures your service requires - Thinking about which errors are recoverable at runtime as opposed to indicating a bug in the customer code that must be fixed -The whole purpose of a preview to address feedback by improving abstractions, naming, relationships, API operations, and so on. It is OK to make breaking changes during a preview to improve the experience now so that it is sustainable long term. - -> :white_check_mark: **DO** provide examples in multiple languages -> -> :white_check_mark: **DO** include at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#). +The whole purpose of a preview to address feedback by improving abstractions, naming, relationships, API operations, and so on. It is OK to make breaking changes during a preview to improve the experience now so that it is sustainable long term. ### Focus on Hero Scenarios It is important to realize that writing an API is, in many cases, the easiest part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, client libraries, examples, blog posts, videos, and supporting customers in perpetuity. In fact, implementing an API is of miniscule cost compared to all the other downstream activities. @@ -46,10 +42,14 @@ Focusing on hero scenarios reduces development, support, and maintenance costs; > > :no_entry: **DO NOT** proactively add APIs for speculative features customers might want +> :white_check_mark: **DO** consider how your abstractions will be represented in different high-level languages. + +> :white_check_mark: **DO** develop code examples in at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#) to illustrate your abstractions and high-level language representations. + ### Start with your API Definition Understanding how your service is used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It reflects the abstractions & naming decisions and makes it easy for developers to implement the hero scenarios. -> :white_check_mark: **DO** provide an [OpenAPI Definition](https://swagger.io/specification/) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. +> :white_check_mark: **DO** provide an [OpenAPI Definition](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. ### Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. @@ -69,7 +69,7 @@ A major inhibitor to adoption and usage is when an API behaves in an unexpected > :ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint __SHOULD__ work with a single type to avoid problems during SDK creation. > -> :ballot_box_with_check: **YOU SHOULD** make [Collections](#Collections) easy to work with. Collections are a common source of review comments. It is important to handle them in a consistent manner within your service. +> :ballot_box_with_check: **YOU SHOULD** make [Collections](./Guidelines.md#collections) easy to work with. Collections are a common source of review comments. It is important to handle them in a consistent manner within your service. > > :ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. > diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 84f363c2..ce2000eb 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -150,7 +150,7 @@ Float | [IEEE-754 binary64](https://en.wikipedia.org/wiki/Double-precision_f String | (Un)quoted?, max length, legal characters, case-sensitive, multiple delimiter UUID | 123e4567-e89b-12d3-a456-426614174000 (no {}s, hyphens, case-insensitive) [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) -Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) +Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ (with at most 3 digits of fractional seconds) [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) Byte array | Base-64 encoded, max length @@ -430,7 +430,7 @@ The REST specification is used to model the state of a resource, and is primaril > > :white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). > -> :white_check_mark: **DO** return a ```nextLink``` field with a URL that the client can GET in order to retrieve the next page of the collection. +> :white_check_mark: **DO** return a ```nextLink``` field with an absolute URL that the client can GET in order to retrieve the next page of the collection. > :ballot_box_with_check: **YOU SHOULD** use ```value``` as the name of the top-level array field unless a more appropriate name is available. @@ -567,12 +567,12 @@ Each expression in the _orderby_ parameter value may include the suffix "asc" fo For example, to return all people sorted by name in ascending order: ```http -GET https://api.contoso.com/people?$orderBy=name +GET https://api.contoso.com/people?orderBy=name ``` For example, to return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. ```http -GET https://api.contoso.com/people?$orderBy=name desc,hireDate +GET https://api.contoso.com/people?orderBy=name desc,hireDate ``` Sorting MUST compose with filtering such that: @@ -621,6 +621,7 @@ Azure services need to change over time. However, when changing a service, there > > :white_check_mark: **DO** use a later date for each new preview version > When releasing a new preview, the service team may completely retire any previous preview versions after giving customers at least 90 days to upgrade their code + > :white_check_mark: **DO** use a later date for successive preview versions. > :no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview 'api-version' is '2021-06-04-preview', the GA version of the API must be a date later than 2021-06-04 @@ -757,7 +758,7 @@ Depending on your requirements, there are scenarios where users of your service > :white_check_mark: **DO** Enable the customer to provide an ETag to specify a specific version of a file. ##### File Collections -When your users need to work with multiple files, for example a document translation service, it will be important to provide them access to the collection, and its contents, in a consistent manner. Because there is no industry standard for working with with containers, these guidelines will recommend that you leverage Azure Storage. Following the guidelines above, you also want to ensure that you don't expose file system constructs, e.g. folders, and instead use storage constructs, e.g. blob prefixes. +When your users need to work with multiple files, for example a document translation service, it will be important to provide them access to the collection, and its contents, in a consistent manner. Because there is no industry standard for working with containers, these guidelines will recommend that you leverage Azure Storage. Following the guidelines above, you also want to ensure that you don't expose file system constructs, e.g. folders, and instead use storage constructs, e.g. blob prefixes. > :white_check_mark: **DO** When using a Shared Access Signature (SAS), ensure this is assigned to the container and that the permissions apply to the content as well. > From 76a03d5622cab30234d913d5dcdd09cbc59e650c Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 9 Jul 2021 16:39:43 -0700 Subject: [PATCH 133/729] More fixes for PR review comments --- azure/Guidelines.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ce2000eb..d18a5499 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -116,7 +116,7 @@ Cloud applications embrace failure. Therefore, to enable customers to write faul > :heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response __must__ return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#Repeatability-of-requests)). ##### HTTP Return Codes -> :white_check_mark: **DO** adhere to the return codes in the following table when the method is successful: +> :white_check_mark: **DO** adhere to the return codes in the following table when the method completes synchronously and is successful: Method | Description | Response Status Code -------|-------------|--------------------- @@ -126,7 +126,9 @@ POST | Create new resource (ID set by service) | 201-Created with URL of creat GET | Read (i.e. list) a resource collection | 200-OK GET | Read the resource | 200-OK DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found - + +> :white_check_mark: **DO** return status code ```202-Accepted``` and follow the guidance in [Long Running Operations & Jobs](#long-running-operations--jobs) when a PUT, POST, or DELETE method completes asynchronously + > :white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase > > :white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a ```200-OK``` or ```201-Created```. @@ -152,6 +154,7 @@ UUID | 123e4567-e89b-12d3-a456-426614174000 (no {}s, hyphens, case-insensit Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ (with at most 3 digits of fractional seconds) [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) Byte array | Base-64 encoded, max length +Array | One of a) a comma-separated list of values (preferred), or b) separate ```name=value``` parameter instances for each value of the array The table below lists the headers most used by Azure services: From b97dd27295f5cd9cf53ae505ec3c9a2566fd106c Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 15 Jul 2021 06:55:28 -0700 Subject: [PATCH 134/729] More fixes for PR review comments --- azure/Guidelines.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index d18a5499..bd25eab7 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -74,12 +74,11 @@ Where: > :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) > -> :ballot_box_with_check: **YOU SHOULD** limit your URL's characters to `0-9 A-Z a-z - . _ ~` +> :ballot_box_with_check: **YOU SHOULD** limit your URL's characters to `0-9 A-Z a-z - . _ ~ :` > :heavy_check_mark: **YOU MAY** use these other characters in the URL but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` - Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' > :white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a JSON response body @@ -199,7 +198,7 @@ retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://da * [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) ### REpresentational State Transfer (REST) -REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. +REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. See [this article in the Azure Architecture Center](https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design) for a more detailed discussion of REST API design patterns. When designing your service, it is important to optimize for the developer using your API. @@ -249,7 +248,7 @@ In addition to the above, a field may be "required" or "optional". A required fi > > :no_entry: **DO NOT** add fields to the JSON if the value is easily computable from other fields to avoid bloating the body. -##### Create / Update / Replease Processing Rules +##### Create / Update / Replace Processing Rules > :white_check_mark: **DO** follow the processing below to create/update/replace a resource: @@ -383,7 +382,7 @@ Below is an example of JSON for a Rectangle and Circle: } } ``` -Both Rectangle and Circle has common fields: ```kind```, ```fillColor```, ```lineColor```, and ```subscription```. A Rectangle also has ```x```, ```y```, ```width```, and ```length``` while a Circle has ```x```, ```y```, and ```radius```. The ```subscription``` is a nested polymorphic type. A ```free``` subscription has no additional fields and a ```paid``` subscription has ```expiration``` and ```invoice``` fields. +Both Rectangle and Circle have common fields: ```kind```, ```fillColor```, ```lineColor```, and ```subscription```. A Rectangle also has ```x```, ```y```, ```width```, and ```length``` while a Circle has ```x```, ```y```, and ```radius```. The ```subscription``` is a nested polymorphic type. A ```free``` subscription has no additional fields and a ```paid``` subscription has ```expiration``` and ```invoice``` fields. ## Common API Patterns @@ -566,7 +565,7 @@ Each expression in the _orderby_ parameter value may include the suffix "asc" fo > > :white_check_mark: **DO** use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically. > -> :white_check_mark: **DO** respond with an error message as defined in the [Unsupported Requests](??) section if the client requests sorting by a field that is not supported by the operation. +> :white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#Handling-errors) section if the client requests sorting by a field that is not supported by the operation. For example, to return all people sorted by name in ascending order: ```http @@ -627,6 +626,8 @@ Azure services need to change over time. However, when changing a service, there > :white_check_mark: **DO** use a later date for successive preview versions. +> :no_entry: **DO NOT** include a version number segment in any operation path. + > :no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview 'api-version' is '2021-06-04-preview', the GA version of the API must be a date later than 2021-06-04 > > :no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. From c531a28c4b31fc1e7b3423ee63b3519138898109 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sat, 17 Jul 2021 07:37:26 -0700 Subject: [PATCH 135/729] More fixes from PR reviews --- azure/ConsiderationsForServiceDesign.md | 6 ++++-- azure/Guidelines.md | 4 +--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 8746943a..6567f6c6 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -29,6 +29,10 @@ It is extremely difficult to create an elegant API that works well on top of a p The whole purpose of a preview to address feedback by improving abstractions, naming, relationships, API operations, and so on. It is OK to make breaking changes during a preview to improve the experience now so that it is sustainable long term. +The API should be designed so that developers can use their language of choice to access the service. + +> :no_entry: **DO** restrict integer values to the range of +/- 253, since JavaScript native types cannot represent integers outside this range. + ### Focus on Hero Scenarios It is important to realize that writing an API is, in many cases, the easiest part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, client libraries, examples, blog posts, videos, and supporting customers in perpetuity. In fact, implementing an API is of miniscule cost compared to all the other downstream activities. @@ -81,8 +85,6 @@ As you build out your service and API, there are a number of decisions that can > :ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. > > :ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. -> -> :ballot_box_with_check: **YOU SHOULD** use wider data types (e.g. 64-bit vs. 32-bit) as they are more future-proof. For example, JavaScript can only support integers up to 253, so relying on the full width of a 64-bit integer should be avoided. ## Getting Help: The Azure REST API Stewardship Board The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index bd25eab7..b1641f64 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -322,12 +322,10 @@ Services, and the clients that access them, may be written in multiple languages > :heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. > -> :heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. +> :heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. Avoid arrays in other situations since arrays can be difficult and inefficient to work with, especially with ```JSON Merge Patch``` where the entire array needs to be read prior to any operation being applied to it. > :ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays whenever possible. -> :warning: **YOU SHOULD NOT** use JSON Arrays, e.g. [ value, … ]. Arrays are very difficult and inefficient to work with, especially with ```JSON Merge Patch```, as the entire array needs to be read prior to any operation being applied to it. - #### Enums & SDKs (Client libraries) It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI definition as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation. From 2e540296e07ea3b0e296333be9de306241a8f0a4 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 21 Jul 2021 07:58:34 -0700 Subject: [PATCH 136/729] More fixes to address PR review comments --- azure/ConsiderationsForServiceDesign.md | 30 ++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 6567f6c6..b96125d5 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -3,10 +3,12 @@ Great APIs make your service usable to customers. They are intuitive, naturally This document provides Microsoft teams building Azure services with a set of guidelines that help service teams build great APIs. The guidelines create APIs that are approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault-tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation enabling a broad audience of developers across multiple languages. -Azure Service teams should engage the Azure HTTP/REST Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. +Azure Service teams should engage the Azure HTTP/REST Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. -Your goal is to create a developer friendly API where: +It is critically important to design your service to avoid disrupting users as the API evolves: +> :white_check_mark: **DO** implement API versioning starting with the very first release of the service. +> > :white_check_mark: **DO** ensure that customer workloads never break > > :white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library without requiring code changes @@ -29,10 +31,6 @@ It is extremely difficult to create an elegant API that works well on top of a p The whole purpose of a preview to address feedback by improving abstractions, naming, relationships, API operations, and so on. It is OK to make breaking changes during a preview to improve the experience now so that it is sustainable long term. -The API should be designed so that developers can use their language of choice to access the service. - -> :no_entry: **DO** restrict integer values to the range of +/- 253, since JavaScript native types cannot represent integers outside this range. - ### Focus on Hero Scenarios It is important to realize that writing an API is, in many cases, the easiest part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, client libraries, examples, blog posts, videos, and supporting customers in perpetuity. In fact, implementing an API is of miniscule cost compared to all the other downstream activities. @@ -43,17 +41,17 @@ Focusing on hero scenarios reduces development, support, and maintenance costs; > :white_check_mark: **DO** define "hero scenarios" first including abstractions, naming, relationships, and then define the API describing the operations required > > :white_check_mark: **DO** provide example code demonstrating the "Hero Scenarios" -> -> :no_entry: **DO NOT** proactively add APIs for speculative features customers might want > :white_check_mark: **DO** consider how your abstractions will be represented in different high-level languages. > :white_check_mark: **DO** develop code examples in at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#) to illustrate your abstractions and high-level language representations. +> :no_entry: **DO NOT** proactively add APIs for speculative features customers might want + ### Start with your API Definition Understanding how your service is used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It reflects the abstractions & naming decisions and makes it easy for developers to implement the hero scenarios. -> :white_check_mark: **DO** provide an [OpenAPI Definition](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. +> :white_check_mark: **DO** create an [OpenAPI Definition](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. ### Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. @@ -71,13 +69,19 @@ Understanding how your service is used and defining its model and interaction pa ### Avoid Surprises A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. +One common area of friction for developers is _polymorphism_ -- where a value may have any of several types or structures. +Polymorphism can be beneficial in certain cases, e.g. as a way to express inheritance, but also creates friction +because it requires the value to be introspected before being processed and cannot be represented in a natural/useful way in many type-safe languages. + > :ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint __SHOULD__ work with a single type to avoid problems during SDK creation. > -> :ballot_box_with_check: **YOU SHOULD** make [Collections](./Guidelines.md#collections) easy to work with. Collections are a common source of review comments. It is important to handle them in a consistent manner within your service. -> -> :ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. +> :ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. > -> :ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. +Collections are another common area of friction for developers. It is important to define collections in a consistent manner within your service and across services of the platform. In particular, features such as pagination, filtering, and sorting, when supported, should follow common API patterns. See [Collections](./Guidelines.md#collections) for specific guidance. + +An important consideration when defining a new service is support for pagination. + +> :ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. See [Collections](./Guidelines.md#collections) for specific guidance. ### Design for Change Resiliency As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. From 1172da7b547b0602e16f4de99bf8c350c0e28fda Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 21 Jul 2021 08:57:01 -0700 Subject: [PATCH 137/729] More fixes to address PR review comments --- azure/ConsiderationsForServiceDesign.md | 5 +++++ azure/Guidelines.md | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index b96125d5..985da2a4 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -21,6 +21,11 @@ A **management plane** API is implemented through the Azure Resource Manager (AR ### Start with the Developer Experience A great API starts with a well thought out and designed service. Your service should define simple/understandable abstractions with each given a clear name that you use consistently throughout your API and documentation. There must also be an unambiguous relationship between these abstractions. +Follow these suggestions to create clear names for your abstractions: +- Don't invent fancy terms or use fancy words. Try explaining the abstraction to someone that is not a domain expert and then name the abstraction using similar verbage. +- Avoid generic names. Names should be specific to the abstraction and highlight how it is different from other abstractions in your service or related services. +- Pick one word/term out of a set of synonyms and stick to it. + It is extremely difficult to create an elegant API that works well on top of a poorly designed service; the service team and customers will live with this pain for years to come. So, the service team should empathize with customers by: - Building apps that consume the API - Hold reviews and share what is learned with your team diff --git a/azure/Guidelines.md b/azure/Guidelines.md index b1641f64..7ef775f1 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -70,7 +70,7 @@ Where: > > :white_check_mark: **DO** return ```414-URI Too Long``` if a URL exceeds 2083 characters > -> :white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request __MUST__ fail with the appropriate HTTP return code. +> :white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request __MUST__ fail with a ```404-Not found``` HTTP return code. > :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) > @@ -335,9 +335,7 @@ This indicates to client libraries and customers that values of the enumeration > :ballot_box_with_check: **YOU SHOULD** use extensible enumerations unless you are positive that the symbol set will NEVER change over time. > > :white_check_mark: **DO** document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow. - -> :no_entry: **DO NOT** send "enum integers" over the wire. -> + > :no_entry: **DO NOT** remove values from your enumeration list as this breaks customer code. #### Polymorphic types From 254b4213894c818efe8fac9e37b5d0a77d758e7e Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 21 Jul 2021 11:47:10 -0700 Subject: [PATCH 138/729] One more tiny edit --- azure/ConsiderationsForServiceDesign.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 985da2a4..3bb61c13 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -21,8 +21,9 @@ A **management plane** API is implemented through the Azure Resource Manager (AR ### Start with the Developer Experience A great API starts with a well thought out and designed service. Your service should define simple/understandable abstractions with each given a clear name that you use consistently throughout your API and documentation. There must also be an unambiguous relationship between these abstractions. -Follow these suggestions to create clear names for your abstractions: +Follow these practices to create clear names for your abstractions: - Don't invent fancy terms or use fancy words. Try explaining the abstraction to someone that is not a domain expert and then name the abstraction using similar verbage. +- Don't include "throwaway" words in names, like "response", "object", "payload", etc. - Avoid generic names. Names should be specific to the abstraction and highlight how it is different from other abstractions in your service or related services. - Pick one word/term out of a set of synonyms and stick to it. From 4d92e2047e549da91fa33eaaf6d6b66e54fa81a1 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 22 Jul 2021 07:57:03 -0700 Subject: [PATCH 139/729] fix orderby casing in examples --- azure/Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 7ef775f1..a7674d7a 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -565,12 +565,12 @@ Each expression in the _orderby_ parameter value may include the suffix "asc" fo For example, to return all people sorted by name in ascending order: ```http -GET https://api.contoso.com/people?orderBy=name +GET https://api.contoso.com/people?orderby=name ``` For example, to return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. ```http -GET https://api.contoso.com/people?orderBy=name desc,hireDate +GET https://api.contoso.com/people?orderby=name desc,hireDate ``` Sorting MUST compose with filtering such that: From c13659469a55db7323d5e9b7b94dea8bde05a654 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 22 Jul 2021 10:42:12 -0700 Subject: [PATCH 140/729] Add guidance to return 404 vs 403 --- azure/Guidelines.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a7674d7a..0431ee17 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -133,6 +133,8 @@ DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found > :white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a ```200-OK``` or ```201-Created```. > > :white_check_mark: **DO** return a ```204-No Content``` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) +> +> :white_check_mark: **DO** return a ```404-Not Found``` when the user does not have access to the resource. Returning a ```404-Not Found``` vs. a ```403-Forbidden``` prevents a malicious request from learning anything about resources based on error codes. > > :white_check_mark: **DO** support caching and optimistic concurrency by honoring the the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers and by returning the etag and last-modified response headers From fdb8576f1087414f6171014f3e674b29fe1ec543 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Tue, 27 Jul 2021 21:05:09 -0500 Subject: [PATCH 141/729] Updates from PR review comments --- azure/ConsiderationsForServiceDesign.md | 8 +++++++- azure/Guidelines.md | 11 ++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 3bb61c13..cb74bfb8 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -16,7 +16,9 @@ It is critically important to design your service to avoid disrupting users as t ### Azure Management Plane vs Data Plane *Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review.* -A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used by subscription administrators. A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful to both administrators and developers. In this case, the operation can appear in both APIs. Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on [the conventions of the Azure ARM](https://github.com/Azure/azure-resource-manager-rpc). +A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used to provision and control the operational state of resources. +A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful for provisioning/control and applications. In this case, the operation can appear in both APIs. Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on [the conventions of the Azure ARM](https://github.com/Azure/azure-resource-manager-rpc). + ### Start with the Developer Experience A great API starts with a well thought out and designed service. Your service should define simple/understandable abstractions with each given a clear name that you use consistently throughout your API and documentation. There must also be an unambiguous relationship between these abstractions. @@ -89,6 +91,10 @@ An important consideration when defining a new service is support for pagination > :ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. See [Collections](./Guidelines.md#collections) for specific guidance. +Another important design pattern for avoiding surprises is idempotency. An operation is idempotent if it can be performed multiple times and have the same result as a single execution. +HTTP requires certain operations like GET, PUT, and DELETE to be idempotent, but for cloud services it is important to make _all_ operations idempotent so that clients can use retry in failure scenarios without risk of unintended consequences. +See the [HTTP Request / Response Pattern section of the Guidelines](./Guidelines.md#http-request--response-pattern) for detailed guidance on making operations idempotent. + ### Design for Change Resiliency As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 0431ee17..dc38f3ca 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -60,7 +60,7 @@ Where: | Field | Description | - | - | | service | Name of the service (ex: blobstore, servicebus, directory, or management) - | cloud | Cloud domain name (see Azure CLI's "az cloud list")

CloudDomain
Publicazure.net
US Governmentusgovcloudapi.net
Chinachinacloudapi.cn
Germancloudapi.de
+ | cloud | Cloud domain name (see Azure CLI's "az cloud list")

CloudDomain
Publicazure.net
| tenant | Globally-unique ID of container representing tenant isolation, billing, enforced quotas, lifetime of containees (ex: subscription UUID) | service‑root | Service-specific path (ex: blobcontainer, myqueue) | resource‑collection | Name of the collection, unabbreviated, pluralized @@ -611,9 +611,11 @@ If supporting _maxpagesize_ ### API Versioning Azure services need to change over time. However, when changing a service, there are 2 requirements: - 1. Already-running customer workloads must never break due to a service change + 1. Already-running customer workloads must not break due to a service change 2. Customers can adopt a new service version without requiring any code changes (Of course, the customer must modify code to leverage any new service features.) +*NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy/) has tables (section 5) describing what kinds of changes are considered breaking. Breaking changes are allowable (due to security/compliance/etc.) if approved by the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) but only following ample communication to customers and a lengthy deprecation period.* + > :white_check_mark: **DO** review any API changes with the Azure API Stewardship Board > > :white_check_mark: **DO** use an 'api-version' query parameter with a date value @@ -624,14 +626,13 @@ Azure services need to change over time. However, when changing a service, there > :white_check_mark: **DO** use a later date for successive preview versions. +> :no_entry: **DO NOT** introduce any breaking changes into the service. + > :no_entry: **DO NOT** include a version number segment in any operation path. > :no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview 'api-version' is '2021-06-04-preview', the GA version of the API must be a date later than 2021-06-04 > > :no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. -> -> :no_entry: **DO NOT** introduce any breaking changes into the service. -*NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy/) has tables (section 5) describing what kinds of changes are considered breaking. If a new service version must break customers (due to security/compliance/etc.), contact the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) as soon as possible.* #### Use Extensible Enums From f8d7ad6ddd85f959b0ce2be91544c09a035570b3 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 30 Jul 2021 15:29:45 -0500 Subject: [PATCH 142/729] More updates for PR review comments --- azure/Guidelines.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index dc38f3ca..122ecf52 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -134,8 +134,8 @@ DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found > > :white_check_mark: **DO** return a ```204-No Content``` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) > -> :white_check_mark: **DO** return a ```404-Not Found``` when the user does not have access to the resource. Returning a ```404-Not Found``` vs. a ```403-Forbidden``` prevents a malicious request from learning anything about resources based on error codes. -> +> :white_check_mark: **DO** return a ```403-Forbidden``` when the user does not have access to the resource _unless_ this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be ```404-Not Found```. [Rationale: a ```403-Forbidden``` is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.] + > :white_check_mark: **DO** support caching and optimistic concurrency by honoring the the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers and by returning the etag and last-modified response headers ### HTTP Query Parameters and Header Values @@ -385,20 +385,28 @@ Both Rectangle and Circle have common fields: ```kind```, ```fillColor```, ```li ## Common API Patterns ### Performing an Action -The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image, sending an SMS message. +The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image, sending an SMS message. It is also sometimes useful to perform an action on a collection. > :white_check_mark: **DO** pattern your URL like this to perform an action on a resource -**URL Pattern** - ```https://...///:?``` - -**SMS Example** +**URL Pattern** +```https://...//:?``` - ```https://.../users/Bob/:send-sms?Text="Hello"``` +**Example** +```https://.../users/Bob:send-sms?text="Hello"``` **Equivalent to (in C#)** ```users["Bob"].SendSms("Hello")``` -> :white_check_mark: **DO** use a POST operation for any action on a resource. +> :white_check_mark: **DO** pattern your URL like this to perform an action on a collection +**URL Pattern** +```https://.../:?``` + +**Example** +```https://.../users:grant?access=read``` + +Note: To avoid potential collision of actions and resource ids, you should disallow the use of the ":" character in resource ids. + +> :white_check_mark: **DO** use a POST operation for any action on a resource or collection. > > :white_check_mark: **DO** support the Repeatability-Request-ID & Repeatability-First-Sent request headers if the action needs to be idempotent if retries occur. From 0c2623b9c9fa022b082d6ff97b9bcd2207604f15 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 30 Jul 2021 16:54:41 -0500 Subject: [PATCH 143/729] Very minor fix --- azure/Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 122ecf52..b4d82aa3 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -74,9 +74,9 @@ Where: > :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) > -> :ballot_box_with_check: **YOU SHOULD** limit your URL's characters to `0-9 A-Z a-z - . _ ~ :` +> :ballot_box_with_check: **YOU SHOULD** limit your URL's path characters to `0-9 A-Z a-z - . _ ~ :` -> :heavy_check_mark: **YOU MAY** use these other characters in the URL but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` +> :heavy_check_mark: **YOU MAY** use these other characters in the URL path but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' From bd60de848b2b0dca03a3e54236fdd0451ea19806 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Tue, 3 Aug 2021 15:29:52 -0500 Subject: [PATCH 144/729] Remove indents on guidelines --- azure/ConsiderationsForServiceDesign.md | 58 +-- azure/Guidelines.md | 462 ++++++++++++------------ 2 files changed, 262 insertions(+), 258 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index cb74bfb8..332e9850 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -7,11 +7,11 @@ Azure Service teams should engage the Azure HTTP/REST Stewardship Board early in It is critically important to design your service to avoid disrupting users as the API evolves: -> :white_check_mark: **DO** implement API versioning starting with the very first release of the service. -> -> :white_check_mark: **DO** ensure that customer workloads never break -> -> :white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library without requiring code changes +:white_check_mark: **DO** implement API versioning starting with the very first release of the service. + +:white_check_mark: **DO** ensure that customer workloads never break + +:white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library without requiring code changes ### Azure Management Plane vs Data Plane *Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review.* @@ -46,33 +46,33 @@ It is important to realize that writing an API is, in many cases, the easiest pa Focusing on hero scenarios reduces development, support, and maintenance costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A telltale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. -> :white_check_mark: **DO** define "hero scenarios" first including abstractions, naming, relationships, and then define the API describing the operations required -> -> :white_check_mark: **DO** provide example code demonstrating the "Hero Scenarios" +:white_check_mark: **DO** define "hero scenarios" first including abstractions, naming, relationships, and then define the API describing the operations required + +:white_check_mark: **DO** provide example code demonstrating the "Hero Scenarios" -> :white_check_mark: **DO** consider how your abstractions will be represented in different high-level languages. +:white_check_mark: **DO** consider how your abstractions will be represented in different high-level languages. -> :white_check_mark: **DO** develop code examples in at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#) to illustrate your abstractions and high-level language representations. +:white_check_mark: **DO** develop code examples in at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#) to illustrate your abstractions and high-level language representations. -> :no_entry: **DO NOT** proactively add APIs for speculative features customers might want +:no_entry: **DO NOT** proactively add APIs for speculative features customers might want ### Start with your API Definition Understanding how your service is used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It reflects the abstractions & naming decisions and makes it easy for developers to implement the hero scenarios. -> :white_check_mark: **DO** create an [OpenAPI Definition](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. +:white_check_mark: **DO** create an [OpenAPI Definition](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. ### Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. -> :ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. -> -> :ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. -> -> :ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. -> -> :ballot_box_with_check: **YOU SHOULD** consider doing a *code with* exercise in which you actively develop with the customer, observing and learning from their API usage. -> -> :ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. +:ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. + +:ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. + +:ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. + +:ballot_box_with_check: **YOU SHOULD** consider doing a *code with* exercise in which you actively develop with the customer, observing and learning from their API usage. + +:ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. ### Avoid Surprises A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. @@ -81,15 +81,15 @@ One common area of friction for developers is _polymorphism_ -- where a value ma Polymorphism can be beneficial in certain cases, e.g. as a way to express inheritance, but also creates friction because it requires the value to be introspected before being processed and cannot be represented in a natural/useful way in many type-safe languages. -> :ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint __SHOULD__ work with a single type to avoid problems during SDK creation. -> -> :ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. -> +:ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint __SHOULD__ work with a single type to avoid problems during SDK creation. + +:ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. + Collections are another common area of friction for developers. It is important to define collections in a consistent manner within your service and across services of the platform. In particular, features such as pagination, filtering, and sorting, when supported, should follow common API patterns. See [Collections](./Guidelines.md#collections) for specific guidance. An important consideration when defining a new service is support for pagination. -> :ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. See [Collections](./Guidelines.md#collections) for specific guidance. +:ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. See [Collections](./Guidelines.md#collections) for specific guidance. Another important design pattern for avoiding surprises is idempotency. An operation is idempotent if it can be performed multiple times and have the same result as a single execution. HTTP requires certain operations like GET, PUT, and DELETE to be idempotent, but for cloud services it is important to make _all_ operations idempotent so that clients can use retry in failure scenarios without risk of unintended consequences. @@ -98,9 +98,9 @@ See the [HTTP Request / Response Pattern section of the Guidelines](./Guidelines ### Design for Change Resiliency As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. -> :ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. -> -> :ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. +:ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. + +:ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. ## Getting Help: The Azure REST API Stewardship Board The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index b4d82aa3..49ddc3e1 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -25,16 +25,16 @@ Technology and software is constantly changing and evolving, and as such, this i ### Prescriptive Guidance This document offers prescriptive guidance labeled as follows: -> :white_check_mark: **DO** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. - -> :ballot_box_with_check: **YOU SHOULD** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. - -> :heavy_check_mark: **YOU MAY** consider this pattern if appropriate to your situation. No notification to the Azure HTTP/REST Stewardship Board is required. - -> :warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. - -> :no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. - +:white_check_mark: **DO** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. + +:ballot_box_with_check: **YOU SHOULD** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. + +:heavy_check_mark: **YOU MAY** consider this pattern if appropriate to your situation. No notification to the Azure HTTP/REST Stewardship Board is required. + +:warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. + +:no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. + *If you feel you need an exception, or need clarity based on your situation, please contact the Azure HTTP/REST Stewardship Board prior to release of your API.* ## Building Blocks: HTTP, REST, & JSON @@ -51,7 +51,7 @@ Azure services must adhere to the HTTP specification, [RFC7231](https://tools.ie A Uniform Resource Locator (URL) is how developers access the resources of your service. Ultimately, URLs are how developers form a cognitive model of your service's resources. -> :white_check_mark: **DO** use this URL pattern: +:white_check_mark: **DO** use this URL pattern: ```text https://.///// ``` @@ -66,26 +66,26 @@ Where: | resource‑collection | Name of the collection, unabbreviated, pluralized | resource‑id | Value of the unique id property. This MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. -> :white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. -> -> :white_check_mark: **DO** return ```414-URI Too Long``` if a URL exceeds 2083 characters -> -> :white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request __MUST__ fail with a ```404-Not found``` HTTP return code. +:white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. + +:white_check_mark: **DO** return ```414-URI Too Long``` if a URL exceeds 2083 characters -> :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) -> -> :ballot_box_with_check: **YOU SHOULD** limit your URL's path characters to `0-9 A-Z a-z - . _ ~ :` +:white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request __MUST__ fail with a ```404-Not found``` HTTP return code. -> :heavy_check_mark: **YOU MAY** use these other characters in the URL path but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` +:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) + +:ballot_box_with_check: **YOU SHOULD** limit your URL's path characters to `0-9 A-Z a-z - . _ ~ :` + +:heavy_check_mark: **YOU MAY** use these other characters in the URL path but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' -> :white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a JSON response body +:white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a JSON response body #### Direct Endpoint URLs -> :heavy_check_mark: **YOU MAY** support a direct endpoint URL for performance/routing: +:heavy_check_mark: **YOU MAY** support a direct endpoint URL for performance/routing: ```text https://-../... ``` @@ -95,9 +95,9 @@ Examples: - Response header ([RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4)): `content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2` - GUID format: `https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2` -> :white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for `` or always a single verified domain. +:white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for `` or always a single verified domain. -> :heavy_check_mark: **YOU MAY** use URLs as values +:heavy_check_mark: **YOU MAY** use URLs as values ``` https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy ``` @@ -108,14 +108,16 @@ The HTTP Request / Response pattern dictates how your API behaves. For example: Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, all service operations (including POST) must be idempotent. Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. #### Exactly Once Behavior = Client Retries & Service Idempotency -> :white_check_mark: **DO** ensure that __all__ HTTP methods are idempotent. - -> :ballot_box_with_check: **YOU SHOULD** use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent. - -> :heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response __must__ return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#Repeatability-of-requests)). + +:white_check_mark: **DO** ensure that __all__ HTTP methods are idempotent. + +:ballot_box_with_check: **YOU SHOULD** use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent. + +:heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response __must__ return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#Repeatability-of-requests)). ##### HTTP Return Codes -> :white_check_mark: **DO** adhere to the return codes in the following table when the method completes synchronously and is successful: + +:white_check_mark: **DO** adhere to the return codes in the following table when the method completes synchronously and is successful: Method | Description | Response Status Code -------|-------------|--------------------- @@ -126,24 +128,24 @@ GET | Read (i.e. list) a resource collection | 200-OK GET | Read the resource | 200-OK DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found -> :white_check_mark: **DO** return status code ```202-Accepted``` and follow the guidance in [Long Running Operations & Jobs](#long-running-operations--jobs) when a PUT, POST, or DELETE method completes asynchronously +:white_check_mark: **DO** return status code ```202-Accepted``` and follow the guidance in [Long Running Operations & Jobs](#long-running-operations--jobs) when a PUT, POST, or DELETE method completes asynchronously + +:white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase + +:white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a ```200-OK``` or ```201-Created```. + +:white_check_mark: **DO** return a ```204-No Content``` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) -> :white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase -> -> :white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a ```200-OK``` or ```201-Created```. -> -> :white_check_mark: **DO** return a ```204-No Content``` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) -> -> :white_check_mark: **DO** return a ```403-Forbidden``` when the user does not have access to the resource _unless_ this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be ```404-Not Found```. [Rationale: a ```403-Forbidden``` is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.] +:white_check_mark: **DO** return a ```403-Forbidden``` when the user does not have access to the resource _unless_ this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be ```404-Not Found```. [Rationale: a ```403-Forbidden``` is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.] -> :white_check_mark: **DO** support caching and optimistic concurrency by honoring the the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers and by returning the etag and last-modified response headers +:white_check_mark: **DO** support caching and optimistic concurrency by honoring the the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers and by returning the etag and last-modified response headers ### HTTP Query Parameters and Header Values Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. -> :white_check_mark: **DO** validate all query parameter and request header values and fail the operation with ```400-Bad Request``` if any value fails validation. Return an error response as described in the [Handling Errors](#Handling-errors) section indicating what is wrong so customer can diagnose the issue and fix it themselves. -> -> :white_check_mark: **DO** use the following table when translating strings: +:white_check_mark: **DO** validate all query parameter and request header values and fail the operation with ```400-Bad Request``` if any value fails validation. Return an error response as described in the [Handling Errors](#Handling-errors) section indicating what is wrong so customer can diagnose the issue and fix it themselves. + +:white_check_mark: **DO** use the following table when translating strings: Data type | Document that string must be --------- | ------- @@ -180,19 +182,19 @@ last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT *x-ms-error-code* | Response | see [Handling Errors](#Handling-Errors) retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)) -> :white_check_mark: **DO** support all headers shown in *italics* -> -> :white_check_mark: **DO** specify headers using kebab-casing -> -> :white_check_mark: **DO** compare request header names using case-insensitivity -> -> :white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it -> -> :white_check_mark: **DO** accept and return date values in headers using the HTTP Date format as defined in [RFC 7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT" +:white_check_mark: **DO** support all headers shown in *italics* + +:white_check_mark: **DO** specify headers using kebab-casing + +:white_check_mark: **DO** compare request header names using case-insensitivity + +:white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it + +:white_check_mark: **DO** accept and return date values in headers using the HTTP Date format as defined in [RFC 7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT" + +:no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated -> :no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated -> -> :no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. +:no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. #### Additional References * [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) @@ -204,21 +206,21 @@ REST is an architectural style with broad reach that emphasizes scalability, gen When designing your service, it is important to optimize for the developer using your API. -> :white_check_mark: **DO** focus heavily on clear & consistent naming -> -> :white_check_mark: **DO** ensure your resource paths make sense -> -> :white_check_mark: **DO** simplify operations with few required query parameters & JSON fields -> -> :white_check_mark: **DO** establish clear contracts for string values -> -> :white_check_mark: **DO** use proper response codes/bodies so customer can diagnose their own problems and fix them without contacting Azure support or the service team +:white_check_mark: **DO** focus heavily on clear & consistent naming + +:white_check_mark: **DO** ensure your resource paths make sense + +:white_check_mark: **DO** simplify operations with few required query parameters & JSON fields + +:white_check_mark: **DO** establish clear contracts for string values + +:white_check_mark: **DO** use proper response codes/bodies so customer can diagnose their own problems and fix them without contacting Azure support or the service team #### JSON Resource Schema & Field Mutability -> :white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH response, GET response, and POST request/response on a given URL path. The PATCH request schema should contain all the same fields with no required fields. This allows one SDK type for input/output operations and enables the response to be passed back in a request. -> -> :white_check_mark: **DO** think about your resource's fields and how they are used: +:white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH response, GET response, and POST request/response on a given URL path. The PATCH request schema should contain all the same fields with no required fields. This allows one SDK type for input/output operations and enables the response to be passed back in a request. + +:white_check_mark: **DO** think about your resource's fields and how they are used: Field Mutability | Service Request's behavior for this field -----------------| ----------------------------------------- @@ -228,31 +230,31 @@ Field Mutability | Service Request's behavior for this field In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically __not__ become a nullable field in a SDK's data structure. This allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. -> :white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. -> -> :white_check_mark: **DO** treat JSON field names with case-sensitivity. -> -> :white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions but avoid if at all possible. -> -> :white_check_mark: **DO** use GET for resource retrieval and return JSON in the response body -> -> :white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. -> -> :white_check_mark: **DO** use PUT with JSON for wholesale create/update operations. *NOTE: If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT).* -> -> :white_check_mark: **DO** use DELETE to remove a resource. -> -> :white_check_mark: **DO** fail an operation with ```400-Bad Request``` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. - -> :heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. - -> :no_entry: **DO NOT** return secret fields via GET. For example, do not return ```administratorPassword``` in JSON. -> -> :no_entry: **DO NOT** add fields to the JSON if the value is easily computable from other fields to avoid bloating the body. +:white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. + +:white_check_mark: **DO** treat JSON field names with case-sensitivity. + +:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions but avoid if at all possible. + +:white_check_mark: **DO** use GET for resource retrieval and return JSON in the response body + +:white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. + +:white_check_mark: **DO** use PUT with JSON for wholesale create/update operations. *NOTE: If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT).* + +:white_check_mark: **DO** use DELETE to remove a resource. + +:white_check_mark: **DO** fail an operation with ```400-Bad Request``` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. + +:heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. + +:no_entry: **DO NOT** return secret fields via GET. For example, do not return ```administratorPassword``` in JSON. + +:no_entry: **DO NOT** add fields to the JSON if the value is easily computable from other fields to avoid bloating the body. ##### Create / Update / Replace Processing Rules - -> :white_check_mark: **DO** follow the processing below to create/update/replace a resource: + +:white_check_mark: **DO** follow the processing below to create/update/replace a resource: When using this method | if this condition happens | use this response code ---------------------- | ------------------------- | ---------------------- @@ -272,16 +274,17 @@ There are 2 kinds of errors: - An error where you expect customer code to gracefully recover at runtime - An error indicating a bug in customer code that is unlikely to be recoverable at runtime; the customer must just fix their code -> :white_check_mark: **DO** return error an ```x-ms-error-code``` response header with a string value indicating what went wrong. -> *NOTE: String values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future.* -> -> :white_check_mark: **DO** carefully craft ```x-ms-error-code``` string values for errors that are recoverable at runtime. -> -> :white_check_mark: **DO** ensure that the top-level ```code``` field's value is identical to the ```x-ms-error-code``` header's value (see example body below). -> -> :white_check_mark: **DO** document the service's error code strings; they are part of the API contract. -> -> :white_check_mark: **DO** provide a response body as follows (example): +:white_check_mark: **DO** return error an ```x-ms-error-code``` response header with a string value indicating what went wrong. + +*NOTE: Error code values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future.* + +:white_check_mark: **DO** carefully craft ```x-ms-error-code``` string values for errors that are recoverable at runtime. + +:white_check_mark: **DO** ensure that the top-level ```code``` field's value is identical to the ```x-ms-error-code``` header's value (see example body below). + +:white_check_mark: **DO** document the service's error code strings; they are part of the API contract. + +:white_check_mark: **DO** provide a response body as follows (example): ```json { "error": { @@ -296,9 +299,9 @@ There are 2 kinds of errors: } ``` -> :heavy_check_mark: **YOU MAY** group common customer code errors into a few ```x-ms-error-code``` string values. -> -> :heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are __not__ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. +:heavy_check_mark: **YOU MAY** group common customer code errors into a few ```x-ms-error-code``` string values. + +:heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are __not__ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. ### JSON @@ -310,23 +313,23 @@ Services, and the clients that access them, may be written in multiple languages Number | Signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) String | Used for everything else -> :white_check_mark: **DO** use integers within the acceptable range of JSON number. -> -> :white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. -> -> :white_check_mark: **DO** use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC3339 for date/time. -> -> :white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable" across multiple programming languages. -> -> :white_check_mark: **DO** use [RFC3339] for date/time. -> -> :white_check_mark: **DO** use [RFC4122] for UUIDs. +:white_check_mark: **DO** use integers within the acceptable range of JSON number. + +:white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. + +:white_check_mark: **DO** use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC3339 for date/time. -> :heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. -> -> :heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. Avoid arrays in other situations since arrays can be difficult and inefficient to work with, especially with ```JSON Merge Patch``` where the entire array needs to be read prior to any operation being applied to it. +:white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable" across multiple programming languages. -> :ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays whenever possible. +:white_check_mark: **DO** use [RFC3339] for date/time. + +:white_check_mark: **DO** use [RFC4122] for UUIDs. + +:heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. + +:heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. Avoid arrays in other situations since arrays can be difficult and inefficient to work with, especially with ```JSON Merge Patch``` where the entire array needs to be read prior to any operation being applied to it. + +:ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays whenever possible. #### Enums & SDKs (Client libraries) It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI definition as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation. @@ -334,18 +337,18 @@ It is common for strings to have an explicit set of values. These are often refl However, it is not uncommon for the set of values to grow over the life of a service. For this reason, Microsoft's tooling uses the concept of an "extensible enum," which indicates that the set of values should be treated as only a *partial* list. This indicates to client libraries and customers that values of the enumeration field should be effectively treated as strings and that undocumented value may returned in the future. This enables the set of values to grow over time while ensuring stability in client libraries and customer code. -> :ballot_box_with_check: **YOU SHOULD** use extensible enumerations unless you are positive that the symbol set will NEVER change over time. -> -> :white_check_mark: **DO** document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow. +:ballot_box_with_check: **YOU SHOULD** use extensible enumerations unless you are positive that the symbol set will NEVER change over time. + +:white_check_mark: **DO** document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow. -> :no_entry: **DO NOT** remove values from your enumeration list as this breaks customer code. +:no_entry: **DO NOT** remove values from your enumeration list as this breaks customer code. #### Polymorphic types -> :warning: **YOU SHOULD NOT** use polymorphic JSON types because they greatly complicate the customer code due to runtime dynamic casts and the introduction of new types in the future. +:warning: **YOU SHOULD NOT** use polymorphic JSON types because they greatly complicate the customer code due to runtime dynamic casts and the introduction of new types in the future. If you can't avoid them, then follow the guideline below. -> :white_check_mark: **DO** define a ```kind``` field indicating the kind of the resource and include any kind-specific fields in the body. +:white_check_mark: **DO** define a ```kind``` field indicating the kind of the resource and include any kind-specific fields in the body. Below is an example of JSON for a Rectangle and Circle: **Rectangle** @@ -387,7 +390,7 @@ Both Rectangle and Circle have common fields: ```kind```, ```fillColor```, ```li ### Performing an Action The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image, sending an SMS message. It is also sometimes useful to perform an action on a collection. -> :white_check_mark: **DO** pattern your URL like this to perform an action on a resource +:white_check_mark: **DO** pattern your URL like this to perform an action on a resource **URL Pattern** ```https://...//:?``` @@ -397,7 +400,7 @@ The REST specification is used to model the state of a resource, and is primaril **Equivalent to (in C#)** ```users["Bob"].SendSms("Hello")``` -> :white_check_mark: **DO** pattern your URL like this to perform an action on a collection +:white_check_mark: **DO** pattern your URL like this to perform an action on a collection **URL Pattern** ```https://.../:?``` @@ -406,20 +409,20 @@ The REST specification is used to model the state of a resource, and is primaril Note: To avoid potential collision of actions and resource ids, you should disallow the use of the ":" character in resource ids. -> :white_check_mark: **DO** use a POST operation for any action on a resource or collection. -> -> :white_check_mark: **DO** support the Repeatability-Request-ID & Repeatability-First-Sent request headers if the action needs to be idempotent if retries occur. +:white_check_mark: **DO** use a POST operation for any action on a resource or collection. + +:white_check_mark: **DO** support the Repeatability-Request-ID & Repeatability-First-Sent request headers if the action needs to be idempotent if retries occur. -> :ballot_box_with_check: **YOU SHOULD** use a verb to name your action. +:ballot_box_with_check: **YOU SHOULD** use a verb to name your action. ### Collections -> :white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources. +:white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources. -> :ballot_box_with_check: **YOU SHOULD** support paging today if there is ever a chance in the future that the number of items can grow to be very large. +:ballot_box_with_check: **YOU SHOULD** support paging today if there is ever a chance in the future that the number of items can grow to be very large. > NOTE: It is a breaking change to add paging in the future -> :heavy_check_mark: **YOU MAY** expose an operation that lists your resources by supporting a GET method with a URL to a resource-collection (as opposed to a resource-id). +:heavy_check_mark: **YOU MAY** expose an operation that lists your resources by supporting a GET method with a URL to a resource-collection (as opposed to a resource-id). **Example Response Body** ``` @@ -434,20 +437,20 @@ Note: To avoid potential collision of actions and resource ids, you should disal } ``` -> :white_check_mark: **DO** include the id field and etag field (if supported) for each item as this allows the customer to modify the item in a future operation. -> -> :white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). -> -> :white_check_mark: **DO** return a ```nextLink``` field with an absolute URL that the client can GET in order to retrieve the next page of the collection. +:white_check_mark: **DO** include the id field and etag field (if supported) for each item as this allows the customer to modify the item in a future operation. -> :ballot_box_with_check: **YOU SHOULD** use ```value``` as the name of the top-level array field unless a more appropriate name is available. +:white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). -> :no_entry: **DO NOT** return the ```nextLink``` field at all when returning the last page of the collection. -> -> :no_entry: **DO NOT** ever return a ```nextLink``` field with a value of null. +:white_check_mark: **DO** return a ```nextLink``` field with an absolute URL that the client can GET in order to retrieve the next page of the collection. + +:ballot_box_with_check: **YOU SHOULD** use ```value``` as the name of the top-level array field unless a more appropriate name is available. + +:no_entry: **DO NOT** return the ```nextLink``` field at all when returning the last page of the collection. + +:no_entry: **DO NOT** ever return a ```nextLink``` field with a value of null. #### Query options -> :heavy_check_mark: **YOU MAY** support the following query parameters allowing customers to control the list operation: +:heavy_check_mark: **YOU MAY** support the following query parameters allowing customers to control the list operation: Parameter name | Type | Description ------------------- | ---- | ----------- @@ -459,23 +462,23 @@ _maxpagesize_ | integer | the maximum number of resources to include _select_ | string array | a list of field names to be returned for each resource _expand_ | string array | a list of the related resources to be included in line with each resource -> :white_check_mark: **DO** return an error if the client specifies any parameter not supported by the service. -> -> :white_check_mark: **DO** treat these query parameter names as case-sensitive. -> -> :white_check_mark: **DO** apply _select_ or _expand_ options after applying all the query options in the table above. -> -> :white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. +:white_check_mark: **DO** return an error if the client specifies any parameter not supported by the service. + +:white_check_mark: **DO** treat these query parameter names as case-sensitive. + +:white_check_mark: **DO** apply _select_ or _expand_ options after applying all the query options in the table above. -> :no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). +:white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. + +:no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). #### filter -> :heavy_check_mark: **YOU MAY** support filtering of the results of a list operation with the _filter_ query parameter. +:heavy_check_mark: **YOU MAY** support filtering of the results of a list operation with the _filter_ query parameter. The value of the _filter_ option is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response. -> :white_check_mark: **DO** omit all resources from the collection for which the _filter_ expression evaluates to false or to null, or references properties that are unavailable due to permissions. +:white_check_mark: **DO** omit all resources from the collection for which the _filter_ expression evaluates to false or to null, or references properties that are unavailable due to permissions. Example: return all Products whose Price is less than $10.00 @@ -485,7 +488,7 @@ GET https://api.contoso.com/products?filter=price lt 10.00 ##### filter operators -> :heavy_check_mark: **YOU MAY** support the following operators in _filter_ expressions: +:heavy_check_mark: **YOU MAY** support the following operators in _filter_ expressions: Operator | Description | Example -------------------- | --------------------- | ----------------------------------------------------- @@ -503,9 +506,9 @@ not | Logical negation | not price le 3.5 __Grouping Operators__ | | ( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 -> :white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](handling-errors) section if a client includes an operator in a _filter_ expression that is not supported by the operation. -> -> :white_check_mark: **DO** use the following operator precedence for supported operators when evaluating _filter_ expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: +:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](handling-errors) section if a client includes an operator in a _filter_ expression that is not supported by the operation. + +:white_check_mark: **DO** use the following operator precedence for supported operators when evaluating _filter_ expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: | Group | Operator | Description | ----------------|----------|------------ @@ -555,7 +558,7 @@ GET https://api.contoso.com/products?filter=(name eq 'Milk' or name eq 'Eggs') a #### orderby -> :heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the _orderby_ query parameter. +:heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the _orderby_ query parameter. *NOTE: It is unusual for a service to support __orderby__ because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results.* The value of the _orderby_ parameter is a comma-separated list of expressions used to sort the items. @@ -563,15 +566,15 @@ A special case of such an expression is a property path terminating on a primiti Each expression in the _orderby_ parameter value may include the suffix "asc" for ascending or "desc" for descending, separated from the expression by one or more spaces. -> :white_check_mark: **DO** sort the collection in ascending order on an expression if "asc" or "desc" is not specified. -> -> :white_check_mark: **DO** sort NULL values as "less than" non-NULL values. -> -> :white_check_mark: **DO** sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on. -> -> :white_check_mark: **DO** use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically. -> -> :white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#Handling-errors) section if the client requests sorting by a field that is not supported by the operation. +:white_check_mark: **DO** sort the collection in ascending order on an expression if "asc" or "desc" is not specified. + +:white_check_mark: **DO** sort NULL values as "less than" non-NULL values. + +:white_check_mark: **DO** sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on. + +:white_check_mark: **DO** use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically. + +:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#Handling-errors) section if the client requests sorting by a field that is not supported by the operation. For example, to return all people sorted by name in ascending order: ```http @@ -591,30 +594,30 @@ will return all people whose name is David sorted in ascending order by hireDate ##### Considerations for sorting with pagination -> :white_check_mark: **DO** use the same filtering options and sort order for all pages of a paginated list operation response. +:white_check_mark: **DO** use the same filtering options and sort order for all pages of a paginated list operation response. ##### skip -> :white_check_mark: **DO** define the _skip_ parameter as an integer with a default and minimum value of 0. +:white_check_mark: **DO** define the _skip_ parameter as an integer with a default and minimum value of 0. -> :heavy_check_mark: **YOU MAY** allow clients to pass the _skip_ query parameter to specify an offset into collection of the first resource to be returned. +:heavy_check_mark: **YOU MAY** allow clients to pass the _skip_ query parameter to specify an offset into collection of the first resource to be returned. ##### top -> :heavy_check_mark: **YOU MAY** allow clients to pass the _top_ query parameter to specify the maximum number of resources to return from the collection. +:heavy_check_mark: **YOU MAY** allow clients to pass the _top_ query parameter to specify the maximum number of resources to return from the collection. If supporting _top_: -> :white_check_mark: **DO** define the _top_ parameter as an integer with a minimum value of 1. If not specified, __top__ has a default value of infinity. -> -> :white_check_mark: **DO** return the collection's _top_ number of resources (if available), starting from _skip_. +:white_check_mark: **DO** define the _top_ parameter as an integer with a minimum value of 1. If not specified, __top__ has a default value of infinity. + +:white_check_mark: **DO** return the collection's _top_ number of resources (if available), starting from _skip_. ##### maxpagesize -> :heavy_check_mark: **YOU MAY** allow clients to pass the _maxpagesize_ query parameter to specify the maximum number of resources to include in a single page response. +:heavy_check_mark: **YOU MAY** allow clients to pass the _maxpagesize_ query parameter to specify the maximum number of resources to include in a single page response. If supporting _maxpagesize_ -> :white_check_mark: **DO** define the _maxpagesize_ parameter as an optional integer with a default value appropriate for the collection. -> -> :white_check_mark: **DO** make clear in documentation of the _maxpagesize_ parameter that the operation may choose to return fewer resources than the value specified. +:white_check_mark: **DO** define the _maxpagesize_ parameter as an optional integer with a default value appropriate for the collection. + +:white_check_mark: **DO** make clear in documentation of the _maxpagesize_ parameter that the operation may choose to return fewer resources than the value specified. ### API Versioning @@ -624,23 +627,23 @@ Azure services need to change over time. However, when changing a service, there *NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy/) has tables (section 5) describing what kinds of changes are considered breaking. Breaking changes are allowable (due to security/compliance/etc.) if approved by the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) but only following ample communication to customers and a lengthy deprecation period.* -> :white_check_mark: **DO** review any API changes with the Azure API Stewardship Board -> -> :white_check_mark: **DO** use an 'api-version' query parameter with a date value +:white_check_mark: **DO** review any API changes with the Azure API Stewardship Board + +:white_check_mark: **DO** use an 'api-version' query parameter with a date value > ```text PUT https://service.azure.com/users/Jeff?api-version=2021-06-04``` -> -> :white_check_mark: **DO** use a later date for each new preview version + +:white_check_mark: **DO** use a later date for each new preview version > When releasing a new preview, the service team may completely retire any previous preview versions after giving customers at least 90 days to upgrade their code -> :white_check_mark: **DO** use a later date for successive preview versions. +:white_check_mark: **DO** use a later date for successive preview versions. + +:no_entry: **DO NOT** introduce any breaking changes into the service. -> :no_entry: **DO NOT** introduce any breaking changes into the service. +:no_entry: **DO NOT** include a version number segment in any operation path. -> :no_entry: **DO NOT** include a version number segment in any operation path. +:no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview 'api-version' is '2021-06-04-preview', the GA version of the API must be a date later than 2021-06-04 -> :no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview 'api-version' is '2021-06-04-preview', the GA version of the API must be a date later than 2021-06-04 -> -> :no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. +:no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. #### Use Extensible Enums @@ -671,7 +674,7 @@ Simpler clients may be hardcoded to a single version of a service. Since Azure s API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. -> :white_check_mark: **DO** support API version discovery, including +:white_check_mark: **DO** support API version discovery, including 1. Support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service @@ -681,7 +684,7 @@ API version discovery is needed when either a given hosted service may expose a 4. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. -> :ballot_box_with_check: **YOU SHOULD** support the following for version discovery: +:ballot_box_with_check: **YOU SHOULD** support the following for version discovery: 1. In addition to the functionality described here, services should support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. @@ -711,7 +714,7 @@ Clients that use version discovery are expected to cache version information. Si The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. -> :ballot_box_with_check: **YOU SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). +:ballot_box_with_check: **YOU SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). - The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. - A service advertises support for repeatability requests by adding the `Repeatability-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. @@ -724,33 +727,33 @@ The ability to retry failed requests for which a client never received a respons The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. -> :white_check_mark: **DO** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. -> -> :white_check_mark: **DO** return the same value for **both** headers. -> -> :white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. +:white_check_mark: **DO** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. + +:white_check_mark: **DO** return the same value for **both** headers. + +:white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. ### Bring your own Storage When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS will provide the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders, nor do they have extensions. -> :white_check_mark: **DO** use Azure Bring Your Own Storage. -> -> :white_check_mark: **DO** use a blob prefix +:white_check_mark: **DO** use Azure Bring Your Own Storage. + +:white_check_mark: **DO** use a blob prefix -> :no_entry: **DO NOT** require a fresh container per operation +:no_entry: **DO NOT** require a fresh container per operation #### Authentication How you secure and protect the data and files that your service uses will not only affect how consumable your API is, but also, how quickly you can evolve and adapt it. Implementing Role Based Access Control [RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) is the recommended approach. It is important to recognize that any roles defined in RBAC essentially become part of your API contract. For example, changing a role's permissions, e.g. restricting access, could effectively cause existing clients to break, as they may no longer have access to necessary resources. -> :white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions. -> -> :white_check_mark: **DO** Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. +:white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions. + +:white_check_mark: **DO** Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. ##### Handling 'downstream' errors It is not uncommon to rely on other services, e.g. storage, when implementing your service. Inevitably, the services you depend on will fail. In these situations, you can include the downstream error code and text in the inner-error of the response body. This provides a consistent pattern for handling errors in the services you depend upon. -> :white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. +:white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. #### Working with files Generally speaking, there are two patterns that you will encounter when working with files; single file access, and file collections. @@ -758,22 +761,22 @@ Generally speaking, there are two patterns that you will encounter when working ##### Single file access Desiging an API for accessing a single file, depending on your scenario, is relatively straight forward. -> :heavy_check_mark: **YOU MAY** use a Shared Access Signature [SAS](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. +:heavy_check_mark: **YOU MAY** use a Shared Access Signature [SAS](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. + +:ballot_box_with_check: **YOU SHOULD** if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text. -> :ballot_box_with_check: **YOU SHOULD** if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text. -> -> :ballot_box_with_check: **YOU SHOULD** support managed identity using Azure Storage by default (if using Azure services). +:ballot_box_with_check: **YOU SHOULD** support managed identity using Azure Storage by default (if using Azure services). ###### File Versioning Depending on your requirements, there are scenarios where users of your service will require a specific version of a file. For example, you may need to keep track of configuration changes over time to be able to rollback to a previous state. In these scenarios, you will need to provide a mechanism for accessing a specific version. -> :white_check_mark: **DO** Enable the customer to provide an ETag to specify a specific version of a file. +:white_check_mark: **DO** Enable the customer to provide an ETag to specify a specific version of a file. ##### File Collections When your users need to work with multiple files, for example a document translation service, it will be important to provide them access to the collection, and its contents, in a consistent manner. Because there is no industry standard for working with containers, these guidelines will recommend that you leverage Azure Storage. Following the guidelines above, you also want to ensure that you don't expose file system constructs, e.g. folders, and instead use storage constructs, e.g. blob prefixes. -> :white_check_mark: **DO** When using a Shared Access Signature (SAS), ensure this is assigned to the container and that the permissions apply to the content as well. -> -> :white_check_mark: **DO** When using managed identity, ensure the customer has given the proper permissions to access the file container to the service. +:white_check_mark: **DO** When using a Shared Access Signature (SAS), ensure this is assigned to the container and that the permissions apply to the content as well. + +:white_check_mark: **DO** When using managed identity, ensure the customer has given the proper permissions to access the file container to the service. A common pattern when working with multiple files is for your service to receive requests that contain the location(s) of files to process, e.g. "input" and a location(s) to place the any files that result from processing, e.g. "output." (Note: the terms "input" and "output" are just examples and terms more relevant to the service domain are more appropriate.) @@ -795,19 +798,20 @@ Note: How the service gets the request body is outside the purview of these guid Depending on the requirements of the service, there can be any number of "input" and "output" sections, including none. However, for each of the "input" sections the following apply: -> :white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter." -> -> :white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```listing``` and ```read``` permissions. +:white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter." + +:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```listing``` and ```read``` permissions. For each of the "output" sections the following apply: -> :white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```write``` permissions +:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```write``` permissions + ### Conditional Requests When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an ```ETag``` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. An ```ETag``` is always set by the service and will enable you to *conditionally* control how your service responds to requests, enabling you to provide predictable updates and more efficient access. -> :ballot_box_with_check: **YOU SHOULD** return an ```ETag``` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). -> -> :ballot_box_with_check: **YOU SHOULD** use ```etags``` consistently across your API, i.e. if you use an ```ETag```, accept it on all other operations. +:ballot_box_with_check: **YOU SHOULD** return an ```ETag``` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). + +:ballot_box_with_check: **YOU SHOULD** use ```etags``` consistently across your API, i.e. if you use an ```ETag```, accept it on all other operations. You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). @@ -816,10 +820,10 @@ One of the more common uses for ```ETag``` headers is cache control, also referr Implementing this strategy is relatively straightforward. First, you will return an ```ETag``` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your ```ETag```. In these scenarios, when a request is made by the client an ```ETag``` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The ```ETag``` value can then be sent in subsequent requests as part of the ```if-none-match``` header. This tells the service to compare the ```ETag``` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated ```ETag``` value in the header. -> :ballot_box_with_check: **YOU SHOULD** implement conditional read strategies +:ballot_box_with_check: **YOU SHOULD** implement conditional read strategies When supporting conditional read strategies: -> :white_check_mark: **DO** adhere to the following table for guidance: +:white_check_mark: **DO** adhere to the following table for guidance: | GET Request | Return code | Response | |:------------|:------------|:--------------------------------------------| @@ -831,10 +835,10 @@ For more control over caching, please refer to the ```cache-control``` [HTTP hea #### Optimistic Concurrency An ```ETag``` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of ```ETags``` and the [HTTP Request / Response Pattern](#http-request--response-pattern). -> :warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. +:warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. When supporting optimistic concurrency: -> :white_check_mark: **DO** adhere to the following table for guidance: +:white_check_mark: **DO** adhere to the following table for guidance: | Operation | Header | Value | etag check | Return code | Response | |:------------|:--------------|:------|:-----------|:------------|----------------| @@ -848,19 +852,19 @@ When supporting optimistic concurrency: #### Computing ETags The strategy that you use to compute the ```ETag``` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the ```ETag```. Another common strategy for determining the value of an ```ETag``` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. -> :ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. +:ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. + +:heavy_check_mark: **YOU MAY** use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. -> :heavy_check_mark: **YOU MAY** use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. -> -> :heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. +:heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. ### Distributed Tracing & Telemetry Azure SDK client guidelines specify that client libraries must send telemetry data through the ```User-Agent``` header, ```X-MS-UserAgent``` header, and Open Telemetry. Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. -> :white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. +:white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. -> :no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. +:no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. #### Additional References * [Azure SDK client guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) * [Azure SDK User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) From 75d629d32be454f181d47772f8adaa5ddd730b31 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 5 Aug 2021 08:55:20 -0500 Subject: [PATCH 145/729] Address JR comments and use consistent casing for ETag --- azure/ConsiderationsForServiceDesign.md | 5 +++++ azure/Guidelines.md | 28 +++++++++++++------------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 332e9850..00154721 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -91,6 +91,11 @@ An important consideration when defining a new service is support for pagination :ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. See [Collections](./Guidelines.md#collections) for specific guidance. +Another consideration for collections is support for sorting the set of returned items with the _orderby_ query parameter. +Sorting collection results can be extremely expensive for a service to implement as it must retrieve all items to sort them. And if the operation supports paging (which is likely), then a client request to get another page may have to retrieve all items and sort them again to determine which items are on the desired page. + +:heavy_check_mark: **YOU MAY** support _orderby_ if customer scenarios really demand it and the service is confident that it can support it in perpetuity (even if the backing storage service changes someday). + Another important design pattern for avoiding surprises is idempotency. An operation is idempotent if it can be performed multiple times and have the same result as a single execution. HTTP requires certain operations like GET, PUT, and DELETE to be idempotent, but for cloud services it is important to make _all_ operations idempotent so that clients can use retry in failure scenarios without risk of unintended consequences. See the [HTTP Request / Response Pattern section of the Guidelines](./Guidelines.md#http-request--response-pattern) for detailed guidance on making operations idempotent. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 49ddc3e1..7774b670 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -103,7 +103,7 @@ https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy ``` ### HTTP Request / Response Pattern -The HTTP Request / Response pattern dictates how your API behaves. For example: POST methods that create resources must be idempotent, GET method results may be cached, the If-Modified and etag headers offer optimistic concurrency. The URL of a service, along with its request/response bodies, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. +The HTTP Request / Response pattern dictates how your API behaves. For example: POST methods that create resources must be idempotent, GET method results may be cached, the If-Modified and ETag headers offer optimistic concurrency. The URL of a service, along with its request/response bodies, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, all service operations (including POST) must be idempotent. Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. @@ -138,7 +138,7 @@ DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found :white_check_mark: **DO** return a ```403-Forbidden``` when the user does not have access to the resource _unless_ this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be ```404-Not Found```. [Rationale: a ```403-Forbidden``` is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.] -:white_check_mark: **DO** support caching and optimistic concurrency by honoring the the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers and by returning the etag and last-modified response headers +:white_check_mark: **DO** support caching and optimistic concurrency by honoring the the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers and by returning the ETag and last-modified response headers ### HTTP Query Parameters and Header Values Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. @@ -177,7 +177,7 @@ date | Both | Sun, 06 Nov 1994 08:49:37 GMT (see [RFC7231, *content-type* | Both | application/merge-patch+json *content-length* | Both | 1024 *x-ms-request-id* | Response | [see Customer Support](http://TODO:link-goes-here) -etag | Response | "67ab43" see [Conditional Requests](#Conditional-Requests) +ETag | Response | "67ab43" see [Conditional Requests](#Conditional-Requests) last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT *x-ms-error-code* | Response | see [Handling Errors](#Handling-Errors) retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)) @@ -437,7 +437,7 @@ Note: To avoid potential collision of actions and resource ids, you should disal } ``` -:white_check_mark: **DO** include the id field and etag field (if supported) for each item as this allows the customer to modify the item in a future operation. +:white_check_mark: **DO** include the _id_ field and _etag_ field (if supported) for each item as this allows the customer to modify the item in a future operation. :white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). @@ -523,6 +523,8 @@ __Grouping Operators__ | | | Conditional AND | and | Logical And | | Conditional OR | or | Logical Or | +> :heavy_check_mark: **YOU MAY** support orderby and filter functions such as concat and contains. For more information, see [odata Canonical Functions](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360979). + ##### Operator examples The following examples illustrate the use and semantics of each of the logical operators. @@ -809,9 +811,9 @@ For each of the "output" sections the following apply: ### Conditional Requests When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an ```ETag``` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. An ```ETag``` is always set by the service and will enable you to *conditionally* control how your service responds to requests, enabling you to provide predictable updates and more efficient access. -:ballot_box_with_check: **YOU SHOULD** return an ```ETag``` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). +:ballot_box_with_check: **YOU SHOULD** return an ```ETag``` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). -:ballot_box_with_check: **YOU SHOULD** use ```etags``` consistently across your API, i.e. if you use an ```ETag```, accept it on all other operations. +:ballot_box_with_check: **YOU SHOULD** use ```ETag```s consistently across your API, i.e. if you use an ```ETag```, accept it on all other operations. You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). @@ -827,8 +829,8 @@ When supporting conditional read strategies: | GET Request | Return code | Response | |:------------|:------------|:--------------------------------------------| -| etag value = if-none-match value | 304 Not Modified | no additional information | -| etag value != if-none-match value | 200 OK | Response body include the serialized value of the resource (typically JSON) | +| ETag value = if-none-match value | 304 Not Modified | no additional information | +| ETag value != if-none-match value | 200 OK | Response body include the serialized value of the resource (typically JSON) | For more control over caching, please refer to the ```cache-control``` [HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). @@ -840,14 +842,14 @@ An ```ETag``` should also be used to reflect the create, update, and delete poli When supporting optimistic concurrency: :white_check_mark: **DO** adhere to the following table for guidance: -| Operation | Header | Value | etag check | Return code | Response | +| Operation | Header | Value | ETag check | Return code | Response | |:------------|:--------------|:------|:-----------|:------------|----------------| | PATCH / PUT | if-none-match | * | check for *any* version of the resource ('*' is a wildcard used to match anything), if none are found, create the resource. | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | | PATCH / PUT | if-none-match | * | check for *any* version of the resource, if one is found, fail the operation | 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| -| PATCH / PUT | if-match | value of etag | value of if-match equals the latest etag value on the server, confirming that the version of the resource is the most current | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | -| PATCH / PUT | if-match | value of etag | value of if-match header DOES NOT equal the latest etag value on the server, indicating a change has ocurred since after the client fetched the resource| 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| -| DELETE | if-none-match | value of etag | value does NOT match the latest value on the server | 412 Preconditioned Failed | Response body SHOULD be empty.| -| DELETE | if-none-match | value of etag | value matches the latest value on the server | 204 No Content | Response body SHOULD be empty. | +| PATCH / PUT | if-match | value of ETag | value of if-match equals the latest ETag value on the server, confirming that the version of the resource is the most current | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | +| PATCH / PUT | if-match | value of ETag | value of if-match header DOES NOT equal the latest ETag value on the server, indicating a change has ocurred since after the client fetched the resource| 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| +| DELETE | if-none-match | value of ETag | value does NOT match the latest value on the server | 412 Preconditioned Failed | Response body SHOULD be empty.| +| DELETE | if-none-match | value of ETag | value matches the latest value on the server | 204 No Content | Response body SHOULD be empty. | #### Computing ETags The strategy that you use to compute the ```ETag``` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the ```ETag```. Another common strategy for determining the value of an ```ETag``` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. From 17568728f9b405768be0e01c78a1a87d14adf139 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 5 Aug 2021 10:37:59 -0500 Subject: [PATCH 146/729] Minor fixes to markdown style --- azure/ConsiderationsForServiceDesign.md | 30 ++--- azure/Guidelines.md | 142 ++++++++++++------------ 2 files changed, 86 insertions(+), 86 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 00154721..a1ecc3f5 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -1,4 +1,4 @@ -## Considerations for Service Design +## Considerations for Service Design Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, especially over time. This document provides Microsoft teams building Azure services with a set of guidelines that help service teams build great APIs. The guidelines create APIs that are approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault-tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation enabling a broad audience of developers across multiple languages. @@ -40,11 +40,11 @@ It is extremely difficult to create an elegant API that works well on top of a p The whole purpose of a preview to address feedback by improving abstractions, naming, relationships, API operations, and so on. It is OK to make breaking changes during a preview to improve the experience now so that it is sustainable long term. ### Focus on Hero Scenarios -It is important to realize that writing an API is, in many cases, the easiest part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, client libraries, examples, blog posts, videos, and supporting customers in perpetuity. In fact, implementing an API is of miniscule cost compared to all the other downstream activities. +It is important to realize that writing an API is, in many cases, the easiest part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, client libraries, examples, blog posts, videos, and supporting customers in perpetuity. In fact, implementing an API is of miniscule cost compared to all the other downstream activities. *For this reason, it is much better to ship with fewer features and only add new features over time as required by customers.* -Focusing on hero scenarios reduces development, support, and maintenance costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A telltale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. +Focusing on hero scenarios reduces development, support, and maintenance costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A telltale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. :white_check_mark: **DO** define "hero scenarios" first including abstractions, naming, relationships, and then define the API describing the operations required @@ -57,12 +57,12 @@ Focusing on hero scenarios reduces development, support, and maintenance costs; :no_entry: **DO NOT** proactively add APIs for speculative features customers might want ### Start with your API Definition -Understanding how your service is used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It reflects the abstractions & naming decisions and makes it easy for developers to implement the hero scenarios. +Understanding how your service is used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It reflects the abstractions & naming decisions and makes it easy for developers to implement the hero scenarios. :white_check_mark: **DO** create an [OpenAPI Definition](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. -### Use Previews to Iterate - Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. +### Use Previews to Iterate +Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. :ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. @@ -81,7 +81,7 @@ One common area of friction for developers is _polymorphism_ -- where a value ma Polymorphism can be beneficial in certain cases, e.g. as a way to express inheritance, but also creates friction because it requires the value to be introspected before being processed and cannot be represented in a natural/useful way in many type-safe languages. -:ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint __SHOULD__ work with a single type to avoid problems during SDK creation. +:ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint **SHOULD** work with a single type to avoid problems during SDK creation. :ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. @@ -100,7 +100,7 @@ Another important design pattern for avoiding surprises is idempotency. An opera HTTP requires certain operations like GET, PUT, and DELETE to be idempotent, but for cloud services it is important to make _all_ operations idempotent so that clients can use retry in failure scenarios without risk of unintended consequences. See the [HTTP Request / Response Pattern section of the Guidelines](./Guidelines.md#http-request--response-pattern) for detailed guidance on making operations idempotent. -### Design for Change Resiliency +### Design for Change Resiliency As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. :ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. @@ -109,12 +109,12 @@ As you build out your service and API, there are a number of decisions that can ## Getting Help: The Azure REST API Stewardship Board The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. - -### Typical Review Session + +### Typical Review Session When engaging with the API REST Stewardship board, your working sessions will generally focus on three areas: -* Correctness - Your service should leverage the proper HTTP verbs, return codes, and respect the core constructs of a REST API, e.g. idempotency, that are standard throughout the industry. -* Consistency - Your services should look and behave as though they are natural part of the Azure platform. -* Well formed - Do your services adhere to REST and Azure standards, e.g. proper return codes, use of headers. -* Durable - Your APIs will grow and change over time and leveraging the common patterns described in this document will help you minimize your tech debt and move fast with confidence. +- Correctness - Your service should leverage the proper HTTP verbs, return codes, and respect the core constructs of a REST API, e.g. idempotency, that are standard throughout the industry. +- Consistency - Your services should look and behave as though they are natural part of the Azure platform. +- Well formed - Do your services adhere to REST and Azure standards, e.g. proper return codes, use of headers. +- Durable - Your APIs will grow and change over time and leveraging the common patterns described in this document will help you minimize your tech debt and move fast with confidence. -It was once said that "all roads lead to Rome." For cloud services, the equivalent might be that "all 'roads' start with your API." That could not be more true than at Microsoft, where client libraries, documentation, and many other artifacts all originate from the fundamental way you choose to expose your service. With careful consideration at the outset of your development effort, the architectural stewardship of the API board, and the thoughtful application of these guidelines, you will be able to produce a consistent, well formed API that will delight our customers. \ No newline at end of file +It was once said that "all roads lead to Rome." For cloud services, the equivalent might be that "all 'roads' start with your API." That could not be more true than at Microsoft, where client libraries, documentation, and many other artifacts all originate from the fundamental way you choose to expose your service. With careful consideration at the outset of your development effort, the architectural stewardship of the API board, and the thoughtful application of these guidelines, you will be able to produce a consistent, well formed API that will delight our customers. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 7774b670..a9ff1d72 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -18,11 +18,11 @@ These guidelines offer prescriptive guidance that Azure service teams MUST follo 1. Customer workloads must never break due to a service change 2. Customers can adopt a version without requiring code changes -Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. +Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. *NOTE: For an existing GA'd service, don't change/break its existing API; instead, leverage these concepts for future APIs while prioritizing consistency within your existing service.* -### Prescriptive Guidance +### Prescriptive Guidance This document offers prescriptive guidance labeled as follows: :white_check_mark: **DO** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. @@ -70,7 +70,7 @@ Where: :white_check_mark: **DO** return ```414-URI Too Long``` if a URL exceeds 2083 characters -:white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request __MUST__ fail with a ```404-Not found``` HTTP return code. +:white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request **MUST** fail with a ```404-Not found``` HTTP return code. :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) @@ -109,24 +109,24 @@ Cloud applications embrace failure. Therefore, to enable customers to write faul #### Exactly Once Behavior = Client Retries & Service Idempotency -:white_check_mark: **DO** ensure that __all__ HTTP methods are idempotent. +:white_check_mark: **DO** ensure that _all_ HTTP methods are idempotent. :ballot_box_with_check: **YOU SHOULD** use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent. -:heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response __must__ return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#Repeatability-of-requests)). +:heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response **MUST** return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#Repeatability-of-requests)). ##### HTTP Return Codes :white_check_mark: **DO** adhere to the return codes in the following table when the method completes synchronously and is successful: -Method | Description | Response Status Code +Method | Description | Response Status Code -------|-------------|--------------------- PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created -PUT | Create/Replace the *whole* resource | 200-OK, 201-Created +PUT | Create/Replace the _whole_ resource | 200-OK, 201-Created POST | Create new resource (ID set by service) | 201-Created with URL of created resource GET | Read (i.e. list) a resource collection | 200-OK -GET | Read the resource | 200-OK -DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found +GET | Read the resource | 200-OK +DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found :white_check_mark: **DO** return status code ```202-Accepted``` and follow the guidance in [Long Running Operations & Jobs](#long-running-operations--jobs) when a PUT, POST, or DELETE method completes asynchronously @@ -164,8 +164,8 @@ The table below lists the headers most used by Azure services: Header Key | Applies to | Example ------------------- | ---------- | ------------- -*authorization* | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) -*x-ms-useragent* | Request | see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry) +_authorization_ | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) +_x-ms-useragent_ | Request | see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry) traceparent | Request | see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry) tracecontext | Request | see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry) accept | Request | application/json @@ -174,15 +174,15 @@ if-none-match | Request | "67ab43" or * (no quotes) (see [Conditional R If-Modified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) If-Unmodified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) date | Both | Sun, 06 Nov 1994 08:49:37 GMT (see [RFC7231, Section 7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2)) -*content-type* | Both | application/merge-patch+json -*content-length* | Both | 1024 -*x-ms-request-id* | Response | [see Customer Support](http://TODO:link-goes-here) +_content-type_ | Both | application/merge-patch+json +_content-length_ | Both | 1024 +_x-ms-request-id_ | Response | [see Customer Support](http://TODO:link-goes-here) ETag | Response | "67ab43" see [Conditional Requests](#Conditional-Requests) last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT -*x-ms-error-code* | Response | see [Handling Errors](#Handling-Errors) +_x-ms-error-code_ | Response | see [Handling Errors](#Handling-Errors) retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)) -:white_check_mark: **DO** support all headers shown in *italics* +:white_check_mark: **DO** support all headers shown in _italics_ :white_check_mark: **DO** specify headers using kebab-casing @@ -226,9 +226,9 @@ Field Mutability | Service Request's behavior for this field -----------------| ----------------------------------------- **Create** | Service honors field only when creating a resource. Minimize create-only fields so customers don't have to delete & re-create the resource. **Update** | Service honors field when creating or updating a resource -**Read** | Service returns this field in a response. If the client passed a read-only field, the service __must__ fail the request unless the passed-in value matches the resource's current value +**Read** | Service returns this field in a response. If the client passed a read-only field, the service **MUST** fail the request unless the passed-in value matches the resource's current value -In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically __not__ become a nullable field in a SDK's data structure. This allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. +In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically _not_ become a nullable field in a SDK's data structure. This allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. :white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. @@ -240,7 +240,7 @@ In addition to the above, a field may be "required" or "optional". A required fi :white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. -:white_check_mark: **DO** use PUT with JSON for wholesale create/update operations. *NOTE: If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT).* +:white_check_mark: **DO** use PUT with JSON for wholesale create/update operations. **NOTE:** If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT). :white_check_mark: **DO** use DELETE to remove a resource. @@ -260,12 +260,12 @@ When using this method | if this condition happens | use this response ---------------------- | ------------------------- | ---------------------- PATCH/PUT | Any JSON field name/value not known/valid | 400-Bad Request PATCH/PUT | Any Read field passed (client can't set Read fields) | 400-Bad Request -| **If the resource does not exist** | +| **If the resource does not exist** | PATCH/PUT | Any mandatory Create/Update field missing | 400-Bad Request PATCH/PUT | Create resource using Create/Update fields | 201-Created | **If the resource already exists** | PATCH | Any Create field doesn't match current value (allows retries) | 409-Conflict -PATCH | Update resource using Update fields | 200-OK +PATCH | Update resource using Update fields | 200-OK PUT | Any mandatory Create/Update field missing | 400-Bad Request PUT | Overwrite resource entirely using Create/Update fields | 200-OK @@ -296,12 +296,12 @@ There are 2 kinds of errors: "minLength": 6, } } -} +} ``` :heavy_check_mark: **YOU MAY** group common customer code errors into a few ```x-ms-error-code``` string values. -:heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are __not__ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. +:heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are _not_ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. ### JSON @@ -334,7 +334,7 @@ Services, and the clients that access them, may be written in multiple languages #### Enums & SDKs (Client libraries) It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI definition as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation. -However, it is not uncommon for the set of values to grow over the life of a service. For this reason, Microsoft's tooling uses the concept of an "extensible enum," which indicates that the set of values should be treated as only a *partial* list. +However, it is not uncommon for the set of values to grow over the life of a service. For this reason, Microsoft's tooling uses the concept of an "extensible enum," which indicates that the set of values should be treated as only a _partial_ list. This indicates to client libraries and customers that values of the enumeration field should be effectively treated as strings and that undocumented value may returned in the future. This enables the set of values to grow over time while ensuring stability in client libraries and customer code. :ballot_box_with_check: **YOU SHOULD** use extensible enumerations unless you are positive that the symbol set will NEVER change over time. @@ -354,13 +354,13 @@ Below is an example of JSON for a Rectangle and Circle: **Rectangle** ```json { - "kind": "rectangle", - "x": 100, - "y": 50, - "width": 10, - "length": 24, - "fillColor": "Red", - "lineColor": "White", + "kind": "rectangle", + "x": 100, + "y": 50, + "width": 10, + "length": 24, + "fillColor": "Red", + "lineColor": "White", "subscription": { "kind": "free" } @@ -371,14 +371,14 @@ Below is an example of JSON for a Rectangle and Circle: ```json { "kind": "circle", - "x": 100, - "y": 50, - "radius": 10, - "fillColor": "Green", - "lineColor": "Black", - "subscription": { - "kind": "paid", - "expiration": "2024", + "x": 100, + "y": 50, + "radius": 10, + "fillColor": "Green", + "lineColor": "Black", + "subscription": { + "kind": "paid", + "expiration": "2024", "invoice": "123456" } } @@ -424,7 +424,7 @@ Note: To avoid potential collision of actions and resource ids, you should disal :heavy_check_mark: **YOU MAY** expose an operation that lists your resources by supporting a GET method with a URL to a resource-collection (as opposed to a resource-id). -**Example Response Body** +**Example Response Body** ``` { "value": [ @@ -492,18 +492,18 @@ GET https://api.contoso.com/products?filter=price lt 10.00 Operator | Description | Example -------------------- | --------------------- | ----------------------------------------------------- -__Comparison Operators__ | | +**Comparison Operators** | | eq | Equal | city eq 'Redmond' ne | Not equal | city ne 'London' gt | Greater than | price gt 20 ge | Greater than or equal | price ge 10 lt | Less than | price lt 20 le | Less than or equal | price le 100 -__Logical Operators__ | | +**Logical Operators** | | and | Logical and | price le 200 and price gt 3.5 or | Logical or | price le 3.5 or price gt 200 not | Logical negation | not price le 3.5 -__Grouping Operators__ | | +**Grouping Operators** | | ( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 :white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](handling-errors) section if a client includes an operator in a _filter_ expression that is not supported by the operation. @@ -561,7 +561,7 @@ GET https://api.contoso.com/products?filter=(name eq 'Milk' or name eq 'Eggs') a #### orderby :heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the _orderby_ query parameter. -*NOTE: It is unusual for a service to support __orderby__ because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results.* +*NOTE: It is unusual for a service to support _orderby_ because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results.* The value of the _orderby_ parameter is a comma-separated list of expressions used to sort the items. A special case of such an expression is a property path terminating on a primitive property. @@ -607,7 +607,7 @@ will return all people whose name is David sorted in ascending order by hireDate :heavy_check_mark: **YOU MAY** allow clients to pass the _top_ query parameter to specify the maximum number of resources to return from the collection. If supporting _top_: -:white_check_mark: **DO** define the _top_ parameter as an integer with a minimum value of 1. If not specified, __top__ has a default value of infinity. +:white_check_mark: **DO** define the _top_ parameter as an integer with a minimum value of 1. If not specified, _top_ has a default value of infinity. :white_check_mark: **DO** return the collection's _top_ number of resources (if available), starting from _skip_. @@ -727,7 +727,7 @@ The ability to retry failed requests for which a client never received a respons ### Long Running Operations & Jobs -The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. +The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. :white_check_mark: **DO** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. @@ -737,7 +737,7 @@ The Microsoft REST API guidelines for Long Running Operations are an updated, cl ### Bring your own Storage -When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS will provide the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders, nor do they have extensions. +When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS will provide the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders, nor do they have extensions. :white_check_mark: **DO** use Azure Bring Your Own Storage. @@ -746,7 +746,7 @@ When implementing your service, it is very common to store and retrieve data and :no_entry: **DO NOT** require a fresh container per operation #### Authentication -How you secure and protect the data and files that your service uses will not only affect how consumable your API is, but also, how quickly you can evolve and adapt it. Implementing Role Based Access Control [RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) is the recommended approach. It is important to recognize that any roles defined in RBAC essentially become part of your API contract. For example, changing a role's permissions, e.g. restricting access, could effectively cause existing clients to break, as they may no longer have access to necessary resources. +How you secure and protect the data and files that your service uses will not only affect how consumable your API is, but also, how quickly you can evolve and adapt it. Implementing Role Based Access Control [RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) is the recommended approach. It is important to recognize that any roles defined in RBAC essentially become part of your API contract. For example, changing a role's permissions, e.g. restricting access, could effectively cause existing clients to break, as they may no longer have access to necessary resources. :white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions. @@ -785,18 +785,18 @@ A common pattern when working with multiple files is for your service to receive For example, in a request payload may look similar to the following: ```json -{ -"input":{ - "location": "https://mycompany.blob.core.windows.net/documents/english/?", - "delimiter":"/" - }, -"output":{ - "location": "https://mycompany.blob.core.windows.net/documents/spanglish/?", - "delimiter":"/" - } -} +{ +"input":{ + "location": "https://mycompany.blob.core.windows.net/documents/english/?", + "delimiter":"/" + }, +"output":{ + "location": "https://mycompany.blob.core.windows.net/documents/spanglish/?", + "delimiter":"/" + } +} ``` -Note: How the service gets the request body is outside the purview of these guidelines. +Note: How the service gets the request body is outside the purview of these guidelines. Depending on the requirements of the service, there can be any number of "input" and "output" sections, including none. However, for each of the "input" sections the following apply: @@ -809,19 +809,19 @@ For each of the "output" sections the following apply: :white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```write``` permissions ### Conditional Requests -When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an ```ETag``` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. An ```ETag``` is always set by the service and will enable you to *conditionally* control how your service responds to requests, enabling you to provide predictable updates and more efficient access. +When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an ```ETag``` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. An ```ETag``` is always set by the service and will enable you to _conditionally_ control how your service responds to requests, enabling you to provide predictable updates and more efficient access. -:ballot_box_with_check: **YOU SHOULD** return an ```ETag``` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). +:ballot_box_with_check: **YOU SHOULD** return an ```ETag``` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). :ballot_box_with_check: **YOU SHOULD** use ```ETag```s consistently across your API, i.e. if you use an ```ETag```, accept it on all other operations. You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). #### Cache Control -One of the more common uses for ```ETag``` headers is cache control, also referred to a "conditional GET." This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). That is, using the value of the ```ETag``` , the server can determine if the resource has changed. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. +One of the more common uses for ```ETag``` headers is cache control, also referred to a "conditional GET." This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). That is, using the value of the ```ETag``` , the server can determine if the resource has changed. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. + +Implementing this strategy is relatively straightforward. First, you will return an ```ETag``` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your ```ETag```. In these scenarios, when a request is made by the client an ```ETag``` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The ```ETag``` value can then be sent in subsequent requests as part of the ```if-none-match``` header. This tells the service to compare the ```ETag``` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated ```ETag``` value in the header. -Implementing this strategy is relatively straightforward. First, you will return an ```ETag``` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your ```ETag```. In these scenarios, when a request is made by the client an ```ETag``` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The ```ETag``` value can then be sent in subsequent requests as part of the ```if-none-match``` header. This tells the service to compare the ```ETag``` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated ```ETag``` value in the header. - :ballot_box_with_check: **YOU SHOULD** implement conditional read strategies When supporting conditional read strategies: @@ -835,7 +835,7 @@ When supporting conditional read strategies: For more control over caching, please refer to the ```cache-control``` [HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). #### Optimistic Concurrency -An ```ETag``` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of ```ETags``` and the [HTTP Request / Response Pattern](#http-request--response-pattern). +An ```ETag``` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of ```ETags``` and the [HTTP Request / Response Pattern](#http-request--response-pattern). :warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. @@ -844,15 +844,15 @@ When supporting optimistic concurrency: | Operation | Header | Value | ETag check | Return code | Response | |:------------|:--------------|:------|:-----------|:------------|----------------| -| PATCH / PUT | if-none-match | * | check for *any* version of the resource ('*' is a wildcard used to match anything), if none are found, create the resource. | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | -| PATCH / PUT | if-none-match | * | check for *any* version of the resource, if one is found, fail the operation | 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| +| PATCH / PUT | if-none-match | * | check for _any_ version of the resource ('*' is a wildcard used to match anything), if none are found, create the resource. | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | +| PATCH / PUT | if-none-match | * | check for _any_ version of the resource, if one is found, fail the operation | 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| | PATCH / PUT | if-match | value of ETag | value of if-match equals the latest ETag value on the server, confirming that the version of the resource is the most current | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | | PATCH / PUT | if-match | value of ETag | value of if-match header DOES NOT equal the latest ETag value on the server, indicating a change has ocurred since after the client fetched the resource| 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| | DELETE | if-none-match | value of ETag | value does NOT match the latest value on the server | 412 Preconditioned Failed | Response body SHOULD be empty.| | DELETE | if-none-match | value of ETag | value matches the latest value on the server | 204 No Content | Response body SHOULD be empty. | #### Computing ETags -The strategy that you use to compute the ```ETag``` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the ```ETag```. Another common strategy for determining the value of an ```ETag``` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. +The strategy that you use to compute the ```ETag``` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the ```ETag```. Another common strategy for determining the value of an ```ETag``` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. :ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. @@ -861,7 +861,7 @@ The strategy that you use to compute the ```ETag``` depends on its semantic. For :heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. ### Distributed Tracing & Telemetry -Azure SDK client guidelines specify that client libraries must send telemetry data through the ```User-Agent``` header, ```X-MS-UserAgent``` header, and Open Telemetry. +Azure SDK client guidelines specify that client libraries must send telemetry data through the ```User-Agent``` header, ```X-MS-UserAgent``` header, and Open Telemetry. Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. :white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. @@ -870,10 +870,10 @@ Client libraries are required to send telemetry and distributed tracing informat #### Additional References * [Azure SDK client guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) * [Azure SDK User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) -* [Azure SDK Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) +* [Azure SDK Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) * [Open Telemetry](https://opentelemetry.io/) ## Final thoughts -These guidelines describe the upfront design considerations, technology building blocks, and common patterns that Azure teams encounter when building an API for their service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. +These guidelines describe the upfront design considerations, technology building blocks, and common patterns that Azure teams encounter when building an API for their service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. For more information on how to part with the Stewardship board, please refer to [Considerations for Service Design](.\ConsiderationsForServiceDesign.md). From 845a20f630f3f8d802e33ff783ec4ec6ec064d4a Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 5 Aug 2021 12:15:34 -0500 Subject: [PATCH 147/729] More markdown style fixes --- azure/ConsiderationsForServiceDesign.md | 6 +- azure/Guidelines.md | 116 +++++++++++++----------- 2 files changed, 67 insertions(+), 55 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index a1ecc3f5..387ba4bd 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -1,5 +1,5 @@ ## Considerations for Service Design -Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, especially over time. +Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, _especially over time_. This document provides Microsoft teams building Azure services with a set of guidelines that help service teams build great APIs. The guidelines create APIs that are approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault-tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation enabling a broad audience of developers across multiple languages. @@ -11,7 +11,7 @@ It is critically important to design your service to avoid disrupting users as t :white_check_mark: **DO** ensure that customer workloads never break -:white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library without requiring code changes +:white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library **without requiring code changes** ### Azure Management Plane vs Data Plane *Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review.* @@ -42,7 +42,7 @@ The whole purpose of a preview to address feedback by improving abstractions, na ### Focus on Hero Scenarios It is important to realize that writing an API is, in many cases, the easiest part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, client libraries, examples, blog posts, videos, and supporting customers in perpetuity. In fact, implementing an API is of miniscule cost compared to all the other downstream activities. -*For this reason, it is much better to ship with fewer features and only add new features over time as required by customers.* +*For this reason, it is **much better** to ship with fewer features and only add new features over time as required by customers.* Focusing on hero scenarios reduces development, support, and maintenance costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A telltale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a9ff1d72..cc3fe127 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -25,7 +25,7 @@ Technology and software is constantly changing and evolving, and as such, this i ### Prescriptive Guidance This document offers prescriptive guidance labeled as follows: -:white_check_mark: **DO** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. +:white_check_mark: **DO** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board **prior** to implementation. :ballot_box_with_check: **YOU SHOULD** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. @@ -33,9 +33,9 @@ This document offers prescriptive guidance labeled as follows: :warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review. -:no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation. +:no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board **prior** to implementation. -*If you feel you need an exception, or need clarity based on your situation, please contact the Azure HTTP/REST Stewardship Board prior to release of your API.* +*If you feel you need an exception, or need clarity based on your situation, please contact the Azure HTTP/REST Stewardship Board **prior** to release of your API.* ## Building Blocks: HTTP, REST, & JSON The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet; namely HTTP, REST, and JSON. This section provides you with a general understanding of how these technologies should be applied when creating your service. @@ -60,7 +60,7 @@ Where: | Field | Description | - | - | | service | Name of the service (ex: blobstore, servicebus, directory, or management) - | cloud | Cloud domain name (see Azure CLI's "az cloud list")

CloudDomain
Publicazure.net
+ | cloud | Cloud domain name, e.g. `azure.net` (see Azure CLI's "az cloud list") | tenant | Globally-unique ID of container representing tenant isolation, billing, enforced quotas, lifetime of containees (ex: subscription UUID) | service‑root | Service-specific path (ex: blobcontainer, myqueue) | resource‑collection | Name of the collection, unabbreviated, pluralized @@ -68,9 +68,9 @@ Where: :white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. -:white_check_mark: **DO** return ```414-URI Too Long``` if a URL exceeds 2083 characters +:white_check_mark: **DO** return `414-URI Too Long` if a URL exceeds 2083 characters -:white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request **MUST** fail with a ```404-Not found``` HTTP return code. +:white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request **MUST** fail with a `404-Not found` HTTP return code. :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) @@ -105,7 +105,7 @@ https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy ### HTTP Request / Response Pattern The HTTP Request / Response pattern dictates how your API behaves. For example: POST methods that create resources must be idempotent, GET method results may be cached, the If-Modified and ETag headers offer optimistic concurrency. The URL of a service, along with its request/response bodies, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. -Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, all service operations (including POST) must be idempotent. Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. +Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, _all_ service operations (including POST) **must** be idempotent. Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. #### Exactly Once Behavior = Client Retries & Service Idempotency @@ -121,29 +121,29 @@ Cloud applications embrace failure. Therefore, to enable customers to write faul Method | Description | Response Status Code -------|-------------|--------------------- -PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK, 201-Created -PUT | Create/Replace the _whole_ resource | 200-OK, 201-Created -POST | Create new resource (ID set by service) | 201-Created with URL of created resource -GET | Read (i.e. list) a resource collection | 200-OK -GET | Read the resource | 200-OK -DELETE | Remove the resource | 204-No Content\; avoid 404-Not Found +PATCH | Create/Modify the resource with JSON Merge Patch | `200-OK`, `201-Created` +PUT | Create/Replace the _whole_ resource | `200-OK`, `201-Created` +POST | Create new resource (ID set by service) | `201-Created` with URL of created resource +GET | Read (i.e. list) a resource collection | `200-OK` +GET | Read the resource | `200-OK` +DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` -:white_check_mark: **DO** return status code ```202-Accepted``` and follow the guidance in [Long Running Operations & Jobs](#long-running-operations--jobs) when a PUT, POST, or DELETE method completes asynchronously +:white_check_mark: **DO** return status code `202-Accepted` and follow the guidance in [Long Running Operations & Jobs](#long-running-operations--jobs) when a PUT, POST, or DELETE method completes asynchronously :white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase -:white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a ```200-OK``` or ```201-Created```. +:white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a `200-OK` or `201-Created`. -:white_check_mark: **DO** return a ```204-No Content``` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return ```404-Not Found```) +:white_check_mark: **DO** return a `204-No Content` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return `404-Not Found`) -:white_check_mark: **DO** return a ```403-Forbidden``` when the user does not have access to the resource _unless_ this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be ```404-Not Found```. [Rationale: a ```403-Forbidden``` is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.] +:white_check_mark: **DO** return a `403-Forbidden` when the user does not have access to the resource _unless_ this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be `404-Not Found`. [Rationale: a `403-Forbidden` is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.] :white_check_mark: **DO** support caching and optimistic concurrency by honoring the the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers and by returning the ETag and last-modified response headers ### HTTP Query Parameters and Header Values Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. -:white_check_mark: **DO** validate all query parameter and request header values and fail the operation with ```400-Bad Request``` if any value fails validation. Return an error response as described in the [Handling Errors](#Handling-errors) section indicating what is wrong so customer can diagnose the issue and fix it themselves. +:white_check_mark: **DO** validate all query parameter and request header values and fail the operation with `400-Bad Request` if any value fails validation. Return an error response as described in the [Handling Errors](#Handling-errors) section indicating what is wrong so customer can diagnose the issue and fix it themselves. :white_check_mark: **DO** use the following table when translating strings: @@ -157,7 +157,7 @@ UUID | 123e4567-e89b-12d3-a456-426614174000 (no {}s, hyphens, case-insensit Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ (with at most 3 digits of fractional seconds) [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) Byte array | Base-64 encoded, max length -Array | One of a) a comma-separated list of values (preferred), or b) separate ```name=value``` parameter instances for each value of the array +Array | One of a) a comma-separated list of values (preferred), or b) separate `name=value` parameter instances for each value of the array The table below lists the headers most used by Azure services: @@ -244,11 +244,11 @@ In addition to the above, a field may be "required" or "optional". A required fi :white_check_mark: **DO** use DELETE to remove a resource. -:white_check_mark: **DO** fail an operation with ```400-Bad Request``` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. +:white_check_mark: **DO** fail an operation with `400-Bad Request` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. :heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. -:no_entry: **DO NOT** return secret fields via GET. For example, do not return ```administratorPassword``` in JSON. +:no_entry: **DO NOT** return secret fields via GET. For example, do not return `administratorPassword` in JSON. :no_entry: **DO NOT** add fields to the JSON if the value is easily computable from other fields to avoid bloating the body. @@ -274,13 +274,13 @@ There are 2 kinds of errors: - An error where you expect customer code to gracefully recover at runtime - An error indicating a bug in customer code that is unlikely to be recoverable at runtime; the customer must just fix their code -:white_check_mark: **DO** return error an ```x-ms-error-code``` response header with a string value indicating what went wrong. +:white_check_mark: **DO** return error an `x-ms-error-code` response header with a string value indicating what went wrong. *NOTE: Error code values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future.* -:white_check_mark: **DO** carefully craft ```x-ms-error-code``` string values for errors that are recoverable at runtime. +:white_check_mark: **DO** carefully craft `x-ms-error-code` string values for errors that are recoverable at runtime. -:white_check_mark: **DO** ensure that the top-level ```code``` field's value is identical to the ```x-ms-error-code``` header's value (see example body below). +:white_check_mark: **DO** ensure that the top-level `code` field's value is identical to the `x-ms-error-code` header's value (see example body below). :white_check_mark: **DO** document the service's error code strings; they are part of the API contract. @@ -299,7 +299,7 @@ There are 2 kinds of errors: } ``` -:heavy_check_mark: **YOU MAY** group common customer code errors into a few ```x-ms-error-code``` string values. +:heavy_check_mark: **YOU MAY** group common customer code errors into a few `x-ms-error-code` string values. :heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are _not_ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. @@ -327,7 +327,7 @@ Services, and the clients that access them, may be written in multiple languages :heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. -:heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. Avoid arrays in other situations since arrays can be difficult and inefficient to work with, especially with ```JSON Merge Patch``` where the entire array needs to be read prior to any operation being applied to it. +:heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. Avoid arrays in other situations since arrays can be difficult and inefficient to work with, especially with JSON Merge Patch where the entire array needs to be read prior to any operation being applied to it. :ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays whenever possible. @@ -348,7 +348,7 @@ This indicates to client libraries and customers that values of the enumeration If you can't avoid them, then follow the guideline below. -:white_check_mark: **DO** define a ```kind``` field indicating the kind of the resource and include any kind-specific fields in the body. +:white_check_mark: **DO** define a `kind` field indicating the kind of the resource and include any kind-specific fields in the body. Below is an example of JSON for a Rectangle and Circle: **Rectangle** @@ -383,7 +383,7 @@ Below is an example of JSON for a Rectangle and Circle: } } ``` -Both Rectangle and Circle have common fields: ```kind```, ```fillColor```, ```lineColor```, and ```subscription```. A Rectangle also has ```x```, ```y```, ```width```, and ```length``` while a Circle has ```x```, ```y```, and ```radius```. The ```subscription``` is a nested polymorphic type. A ```free``` subscription has no additional fields and a ```paid``` subscription has ```expiration``` and ```invoice``` fields. +Both Rectangle and Circle have common fields: `kind`, `fillColor`, `lineColor`, and `subscription`. A Rectangle also has `x`, `y`, `width`, and `length` while a Circle has `x`, `y`, and `radius`. The `subscription` is a nested polymorphic type. A `free` subscription has no additional fields and a `paid` subscription has `expiration` and `invoice` fields. ## Common API Patterns @@ -392,20 +392,28 @@ The REST specification is used to model the state of a resource, and is primaril :white_check_mark: **DO** pattern your URL like this to perform an action on a resource **URL Pattern** -```https://...//:?``` +```http +https://...//:? +``` **Example** -```https://.../users/Bob:send-sms?text="Hello"``` +```http +https://.../users/Bob:send-sms?text="Hello" +``` **Equivalent to (in C#)** ```users["Bob"].SendSms("Hello")``` :white_check_mark: **DO** pattern your URL like this to perform an action on a collection **URL Pattern** -```https://.../:?``` +```http +https://.../:? +``` **Example** -```https://.../users:grant?access=read``` +```http +https://.../users:grant?access=read +``` Note: To avoid potential collision of actions and resource ids, you should disallow the use of the ":" character in resource ids. @@ -441,13 +449,13 @@ Note: To avoid potential collision of actions and resource ids, you should disal :white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). -:white_check_mark: **DO** return a ```nextLink``` field with an absolute URL that the client can GET in order to retrieve the next page of the collection. +:white_check_mark: **DO** return a `nextLink` field with an absolute URL that the client can GET in order to retrieve the next page of the collection. -:ballot_box_with_check: **YOU SHOULD** use ```value``` as the name of the top-level array field unless a more appropriate name is available. +:ballot_box_with_check: **YOU SHOULD** use `value` as the name of the top-level array field unless a more appropriate name is available. -:no_entry: **DO NOT** return the ```nextLink``` field at all when returning the last page of the collection. +:no_entry: **DO NOT** return the `nextLink` field at all when returning the last page of the collection. -:no_entry: **DO NOT** ever return a ```nextLink``` field with a value of null. +:no_entry: **DO NOT** ever return a `nextLink` field with a value of null. #### Query options :heavy_check_mark: **YOU MAY** support the following query parameters allowing customers to control the list operation: @@ -632,10 +640,14 @@ Azure services need to change over time. However, when changing a service, there :white_check_mark: **DO** review any API changes with the Azure API Stewardship Board :white_check_mark: **DO** use an 'api-version' query parameter with a date value -> ```text PUT https://service.azure.com/users/Jeff?api-version=2021-06-04``` + +```http +PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 +``` :white_check_mark: **DO** use a later date for each new preview version -> When releasing a new preview, the service team may completely retire any previous preview versions after giving customers at least 90 days to upgrade their code + +When releasing a new preview, the service team may completely retire any previous preview versions after giving customers at least 90 days to upgrade their code :white_check_mark: **DO** use a later date for successive preview versions. @@ -643,7 +655,7 @@ Azure services need to change over time. However, when changing a service, there :no_entry: **DO NOT** include a version number segment in any operation path. -:no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview 'api-version' is '2021-06-04-preview', the GA version of the API must be a date later than 2021-06-04 +:no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview 'api-version' is '2021-06-04-preview', the GA version of the API **must be** a date later than 2021-06-04 :no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. @@ -802,25 +814,25 @@ Depending on the requirements of the service, there can be any number of "input" :white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter." -:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```listing``` and ```read``` permissions. +:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of `listing` and `read` permissions. For each of the "output" sections the following apply: -:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of ```write``` permissions +:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of `write` permissions ### Conditional Requests -When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an ```ETag``` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. An ```ETag``` is always set by the service and will enable you to _conditionally_ control how your service responds to requests, enabling you to provide predictable updates and more efficient access. +When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an `ETag` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. An `ETag` is always set by the service and will enable you to _conditionally_ control how your service responds to requests, enabling you to provide predictable updates and more efficient access. -:ballot_box_with_check: **YOU SHOULD** return an ```ETag``` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). +:ballot_box_with_check: **YOU SHOULD** return an `ETag` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). -:ballot_box_with_check: **YOU SHOULD** use ```ETag```s consistently across your API, i.e. if you use an ```ETag```, accept it on all other operations. +:ballot_box_with_check: **YOU SHOULD** use `ETag`s consistently across your API, i.e. if you use an `ETag`, accept it on all other operations. You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). #### Cache Control -One of the more common uses for ```ETag``` headers is cache control, also referred to a "conditional GET." This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). That is, using the value of the ```ETag``` , the server can determine if the resource has changed. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. +One of the more common uses for `ETag` headers is cache control, also referred to a "conditional GET." This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). That is, using the value of the `ETag` , the server can determine if the resource has changed. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. -Implementing this strategy is relatively straightforward. First, you will return an ```ETag``` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your ```ETag```. In these scenarios, when a request is made by the client an ```ETag``` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The ```ETag``` value can then be sent in subsequent requests as part of the ```if-none-match``` header. This tells the service to compare the ```ETag``` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated ```ETag``` value in the header. +Implementing this strategy is relatively straightforward. First, you will return an `ETag` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your `ETag`. In these scenarios, when a request is made by the client an `ETag` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The `ETag` value can then be sent in subsequent requests as part of the `if-none-match` header. This tells the service to compare the `ETag` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated `ETag` value in the header. :ballot_box_with_check: **YOU SHOULD** implement conditional read strategies @@ -832,10 +844,10 @@ When supporting conditional read strategies: | ETag value = if-none-match value | 304 Not Modified | no additional information | | ETag value != if-none-match value | 200 OK | Response body include the serialized value of the resource (typically JSON) | -For more control over caching, please refer to the ```cache-control``` [HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). +For more control over caching, please refer to the `cache-control` [HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). #### Optimistic Concurrency -An ```ETag``` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of ```ETags``` and the [HTTP Request / Response Pattern](#http-request--response-pattern). +An `ETag` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of `ETags` and the [HTTP Request / Response Pattern](#http-request--response-pattern). :warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. @@ -844,15 +856,15 @@ When supporting optimistic concurrency: | Operation | Header | Value | ETag check | Return code | Response | |:------------|:--------------|:------|:-----------|:------------|----------------| -| PATCH / PUT | if-none-match | * | check for _any_ version of the resource ('*' is a wildcard used to match anything), if none are found, create the resource. | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | +| PATCH / PUT | if-none-match | * | check for _any_ version of the resource ('*' is a wildcard used to match anything), if none are found, create the resource. | 200 OK or
201 Created
| Response header MUST include the new `ETag` value. Response body SHOULD include the serialized value of the resource (typically JSON). | | PATCH / PUT | if-none-match | * | check for _any_ version of the resource, if one is found, fail the operation | 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| -| PATCH / PUT | if-match | value of ETag | value of if-match equals the latest ETag value on the server, confirming that the version of the resource is the most current | 200 OK or
201 Created
| Response header MUST include the new ```ETag``` value. Response body SHOULD include the serialized value of the resource (typically JSON). | +| PATCH / PUT | if-match | value of ETag | value of if-match equals the latest ETag value on the server, confirming that the version of the resource is the most current | 200 OK or
201 Created
| Response header MUST include the new `ETag` value. Response body SHOULD include the serialized value of the resource (typically JSON). | | PATCH / PUT | if-match | value of ETag | value of if-match header DOES NOT equal the latest ETag value on the server, indicating a change has ocurred since after the client fetched the resource| 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| | DELETE | if-none-match | value of ETag | value does NOT match the latest value on the server | 412 Preconditioned Failed | Response body SHOULD be empty.| | DELETE | if-none-match | value of ETag | value matches the latest value on the server | 204 No Content | Response body SHOULD be empty. | #### Computing ETags -The strategy that you use to compute the ```ETag``` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the ```ETag```. Another common strategy for determining the value of an ```ETag``` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. +The strategy that you use to compute the `ETag` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the `ETag`. Another common strategy for determining the value of an `ETag` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. :ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. @@ -861,7 +873,7 @@ The strategy that you use to compute the ```ETag``` depends on its semantic. For :heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. ### Distributed Tracing & Telemetry -Azure SDK client guidelines specify that client libraries must send telemetry data through the ```User-Agent``` header, ```X-MS-UserAgent``` header, and Open Telemetry. +Azure SDK client guidelines specify that client libraries must send telemetry data through the `User-Agent` header, `X-MS-UserAgent` header, and Open Telemetry. Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. :white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. From 052413171963b45474fcd23f4e6422321b3e641f Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 5 Aug 2021 12:25:40 -0500 Subject: [PATCH 148/729] Use code casing --- azure/ConsiderationsForServiceDesign.md | 2 +- azure/Guidelines.md | 80 ++++++++++++------------- 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 387ba4bd..a19ae389 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -94,7 +94,7 @@ An important consideration when defining a new service is support for pagination Another consideration for collections is support for sorting the set of returned items with the _orderby_ query parameter. Sorting collection results can be extremely expensive for a service to implement as it must retrieve all items to sort them. And if the operation supports paging (which is likely), then a client request to get another page may have to retrieve all items and sort them again to determine which items are on the desired page. -:heavy_check_mark: **YOU MAY** support _orderby_ if customer scenarios really demand it and the service is confident that it can support it in perpetuity (even if the backing storage service changes someday). +:heavy_check_mark: **YOU MAY** support `orderby` if customer scenarios really demand it and the service is confident that it can support it in perpetuity (even if the backing storage service changes someday). Another important design pattern for avoiding surprises is idempotency. An operation is idempotent if it can be performed multiple times and have the same result as a single execution. HTTP requires certain operations like GET, PUT, and DELETE to be idempotent, but for cloud services it is important to make _all_ operations idempotent so that clients can use retry in failure scenarios without risk of unintended consequences. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index cc3fe127..8ffcce91 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -462,41 +462,41 @@ Note: To avoid potential collision of actions and resource ids, you should disal Parameter name | Type | Description ------------------- | ---- | ----------- -_filter_ | string | an expression on the resource type that selects the resources to be returned -_orderby_ | string array | a list of expressions that specify the order of the returned resources -_skip_ | integer | an offset into the collection of the first resource to be returned -_top_ | integer | the maximum number of resources to return from the collection -_maxpagesize_ | integer | the maximum number of resources to include in a single response -_select_ | string array | a list of field names to be returned for each resource -_expand_ | string array | a list of the related resources to be included in line with each resource +`filter` | string | an expression on the resource type that selects the resources to be returned +`orderby` | string array | a list of expressions that specify the order of the returned resources +`skip` | integer | an offset into the collection of the first resource to be returned +`top` | integer | the maximum number of resources to return from the collection +`maxpagesize` | integer | the maximum number of resources to include in a single response +`select` | string array | a list of field names to be returned for each resource +`expand` | string array | a list of the related resources to be included in line with each resource :white_check_mark: **DO** return an error if the client specifies any parameter not supported by the service. :white_check_mark: **DO** treat these query parameter names as case-sensitive. -:white_check_mark: **DO** apply _select_ or _expand_ options after applying all the query options in the table above. +:white_check_mark: **DO** apply `select` or `expand` options after applying all the query options in the table above. :white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. :no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). -#### filter +#### `filter` -:heavy_check_mark: **YOU MAY** support filtering of the results of a list operation with the _filter_ query parameter. +:heavy_check_mark: **YOU MAY** support `filter`ing of the results of a list operation with the `filter` query parameter. -The value of the _filter_ option is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response. +The value of the `filter` option is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response. -:white_check_mark: **DO** omit all resources from the collection for which the _filter_ expression evaluates to false or to null, or references properties that are unavailable due to permissions. +:white_check_mark: **DO** omit all resources from the collection for which the `filter` expression evaluates to false or to null, or references properties that are unavailable due to permissions. Example: return all Products whose Price is less than $10.00 ```http -GET https://api.contoso.com/products?filter=price lt 10.00 +GET https://api.contoso.com/products?`filter`=price lt 10.00 ``` -##### filter operators +##### `filter` operators -:heavy_check_mark: **YOU MAY** support the following operators in _filter_ expressions: +:heavy_check_mark: **YOU MAY** support the following operators in `filter` expressions: Operator | Description | Example -------------------- | --------------------- | ----------------------------------------------------- @@ -514,9 +514,9 @@ not | Logical negation | not price le 3.5 **Grouping Operators** | | ( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 -:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](handling-errors) section if a client includes an operator in a _filter_ expression that is not supported by the operation. +:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](handling-errors) section if a client includes an operator in a `filter` expression that is not supported by the operation. -:white_check_mark: **DO** use the following operator precedence for supported operators when evaluating _filter_ expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: +:white_check_mark: **DO** use the following operator precedence for supported operators when evaluating `filter` expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: | Group | Operator | Description | ----------------|----------|------------ @@ -531,7 +531,7 @@ not | Logical negation | not price le 3.5 | Conditional AND | and | Logical And | | Conditional OR | or | Logical Or | -> :heavy_check_mark: **YOU MAY** support orderby and filter functions such as concat and contains. For more information, see [odata Canonical Functions](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360979). +> :heavy_check_mark: **YOU MAY** support orderby and `filter` functions such as concat and contains. For more information, see [odata Canonical Functions](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360979). ##### Operator examples The following examples illustrate the use and semantics of each of the logical operators. @@ -539,42 +539,42 @@ The following examples illustrate the use and semantics of each of the logical o Example: all products with a name equal to 'Milk' ```http -GET https://api.contoso.com/products?filter=name eq 'Milk' +GET https://api.contoso.com/products?`filter`=name eq 'Milk' ``` Example: all products with a name not equal to 'Milk' ```http -GET https://api.contoso.com/products?filter=name ne 'Milk' +GET https://api.contoso.com/products?`filter`=name ne 'Milk' ``` Example: all products with the name 'Milk' that also have a price less than 2.55: ```http -GET https://api.contoso.com/products?filter=name eq 'Milk' and price lt 2.55 +GET https://api.contoso.com/products?`filter`=name eq 'Milk' and price lt 2.55 ``` Example: all products that either have the name 'Milk' or have a price less than 2.55: ```http -GET https://api.contoso.com/products?filter=name eq 'Milk' or price lt 2.55 +GET https://api.contoso.com/products?`filter`=name eq 'Milk' or price lt 2.55 ``` Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: ```http -GET https://api.contoso.com/products?filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 +GET https://api.contoso.com/products?`filter`=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 ``` #### orderby -:heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the _orderby_ query parameter. -*NOTE: It is unusual for a service to support _orderby_ because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results.* +:heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the `orderby` query parameter. +*NOTE: It is unusual for a service to support `orderby` because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results.* -The value of the _orderby_ parameter is a comma-separated list of expressions used to sort the items. +The value of the `orderby` parameter is a comma-separated list of expressions used to sort the items. A special case of such an expression is a property path terminating on a primitive property. -Each expression in the _orderby_ parameter value may include the suffix "asc" for ascending or "desc" for descending, separated from the expression by one or more spaces. +Each expression in the `orderby` parameter value may include the suffix "asc" for ascending or "desc" for descending, separated from the expression by one or more spaces. :white_check_mark: **DO** sort the collection in ascending order on an expression if "asc" or "desc" is not specified. @@ -596,38 +596,36 @@ For example, to return all people sorted by name in descending order and a secon GET https://api.contoso.com/people?orderby=name desc,hireDate ``` -Sorting MUST compose with filtering such that: +Sorting MUST compose with `filter`ing such that: ```http -GET https://api.contoso.com/people?filter=name eq 'david'&orderby=hireDate +GET https://api.contoso.com/people?`filter`=name eq 'david'&orderby=hireDate ``` will return all people whose name is David sorted in ascending order by hireDate. ##### Considerations for sorting with pagination -:white_check_mark: **DO** use the same filtering options and sort order for all pages of a paginated list operation response. +:white_check_mark: **DO** use the same `filter`ing options and sort order for all pages of a paginated list operation response. ##### skip -:white_check_mark: **DO** define the _skip_ parameter as an integer with a default and minimum value of 0. +:white_check_mark: **DO** define the `skip` parameter as an integer with a default and minimum value of 0. -:heavy_check_mark: **YOU MAY** allow clients to pass the _skip_ query parameter to specify an offset into collection of the first resource to be returned. +:heavy_check_mark: **YOU MAY** allow clients to pass the `skip` query parameter to specify an offset into collection of the first resource to be returned. ##### top -:heavy_check_mark: **YOU MAY** allow clients to pass the _top_ query parameter to specify the maximum number of resources to return from the collection. +:heavy_check_mark: **YOU MAY** allow clients to pass the `top` query parameter to specify the maximum number of resources to return from the collection. -If supporting _top_: -:white_check_mark: **DO** define the _top_ parameter as an integer with a minimum value of 1. If not specified, _top_ has a default value of infinity. +If supporting `top`: +:white_check_mark: **DO** define the `top` parameter as an integer with a minimum value of 1. If not specified, `top` has a default value of infinity. -:white_check_mark: **DO** return the collection's _top_ number of resources (if available), starting from _skip_. +:white_check_mark: **DO** return the collection's `top` number of resources (if available), starting from `skip`. ##### maxpagesize -:heavy_check_mark: **YOU MAY** allow clients to pass the _maxpagesize_ query parameter to specify the maximum number of resources to include in a single page response. +:heavy_check_mark: **YOU MAY** allow clients to pass the `maxpagesize` query parameter to specify the maximum number of resources to include in a single page response. -If supporting _maxpagesize_ +:white_check_mark: **DO** define the `maxpagesize` parameter as an optional integer with a default value appropriate for the collection. -:white_check_mark: **DO** define the _maxpagesize_ parameter as an optional integer with a default value appropriate for the collection. - -:white_check_mark: **DO** make clear in documentation of the _maxpagesize_ parameter that the operation may choose to return fewer resources than the value specified. +:white_check_mark: **DO** make clear in documentation of the `maxpagesize` parameter that the operation may choose to return fewer resources than the value specified. ### API Versioning From c37fbfa3f5e5035ffc1f13fbba3090e5dad39d4a Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 5 Aug 2021 12:50:13 -0500 Subject: [PATCH 149/729] Last round (hopefully) of style changes --- azure/Guidelines.md | 58 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 8ffcce91..20d91280 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -138,7 +138,7 @@ DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` :white_check_mark: **DO** return a `403-Forbidden` when the user does not have access to the resource _unless_ this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be `404-Not Found`. [Rationale: a `403-Forbidden` is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.] -:white_check_mark: **DO** support caching and optimistic concurrency by honoring the the if-match, if-none-match, if-modified-since, and if-unmodified-since request headers and by returning the ETag and last-modified response headers +:white_check_mark: **DO** support caching and optimistic concurrency by honoring the the `If-Match`, `If-None-Match`, if-modified-since, and if-unmodified-since request headers and by returning the ETag and last-modified response headers ### HTTP Query Parameters and Header Values Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. @@ -165,21 +165,21 @@ The table below lists the headers most used by Azure services: Header Key | Applies to | Example ------------------- | ---------- | ------------- _authorization_ | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) -_x-ms-useragent_ | Request | see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry) -traceparent | Request | see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry) -tracecontext | Request | see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry) +_x-ms-useragent_ | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry)) +traceparent | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry)) +tracecontext | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry)) accept | Request | application/json -if-match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) -if-none-match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) +If-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) +If-None-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) If-Modified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) If-Unmodified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) date | Both | Sun, 06 Nov 1994 08:49:37 GMT (see [RFC7231, Section 7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2)) _content-type_ | Both | application/merge-patch+json _content-length_ | Both | 1024 _x-ms-request-id_ | Response | [see Customer Support](http://TODO:link-goes-here) -ETag | Response | "67ab43" see [Conditional Requests](#Conditional-Requests) +ETag | Response | "67ab43" (see [Conditional Requests](#Conditional-Requests)) last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT -_x-ms-error-code_ | Response | see [Handling Errors](#Handling-Errors) +_x-ms-error-code_ | Response | (see [Handling Errors](#Handling-Errors)) retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)) :white_check_mark: **DO** support all headers shown in _italics_ @@ -258,16 +258,16 @@ In addition to the above, a field may be "required" or "optional". A required fi When using this method | if this condition happens | use this response code ---------------------- | ------------------------- | ---------------------- -PATCH/PUT | Any JSON field name/value not known/valid | 400-Bad Request -PATCH/PUT | Any Read field passed (client can't set Read fields) | 400-Bad Request +PATCH/PUT | Any JSON field name/value not known/valid | `400-Bad Request` +PATCH/PUT | Any Read field passed (client can't set Read fields) | `400-Bad Request` | **If the resource does not exist** | -PATCH/PUT | Any mandatory Create/Update field missing | 400-Bad Request -PATCH/PUT | Create resource using Create/Update fields | 201-Created +PATCH/PUT | Any mandatory Create/Update field missing | `400-Bad Request` +PATCH/PUT | Create resource using Create/Update fields | `201-Created` | **If the resource already exists** | -PATCH | Any Create field doesn't match current value (allows retries) | 409-Conflict -PATCH | Update resource using Update fields | 200-OK -PUT | Any mandatory Create/Update field missing | 400-Bad Request -PUT | Overwrite resource entirely using Create/Update fields | 200-OK +PATCH | Any Create field doesn't match current value (allows retries) | `409-Conflict` +PATCH | Update resource using Update fields | `200-OK` +PUT | Any mandatory Create/Update field missing | `400-Bad Request` +PUT | Overwrite resource entirely using Create/Update fields | `200-OK` #### Handling Errors There are 2 kinds of errors: @@ -321,9 +321,9 @@ Services, and the clients that access them, may be written in multiple languages :white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable" across multiple programming languages. -:white_check_mark: **DO** use [RFC3339] for date/time. +:white_check_mark: **DO** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) for date/time. -:white_check_mark: **DO** use [RFC4122] for UUIDs. +:white_check_mark: **DO** use [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) for UUIDs. :heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. @@ -637,7 +637,7 @@ Azure services need to change over time. However, when changing a service, there :white_check_mark: **DO** review any API changes with the Azure API Stewardship Board -:white_check_mark: **DO** use an 'api-version' query parameter with a date value +:white_check_mark: **DO** use an `api-version` query parameter with a date value ```http PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 @@ -653,7 +653,7 @@ When releasing a new preview, the service team may completely retire any previou :no_entry: **DO NOT** include a version number segment in any operation path. -:no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview 'api-version' is '2021-06-04-preview', the GA version of the API **must be** a date later than 2021-06-04 +:no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview `api-version` is '2021-06-04-preview', the GA version of the API **must be** a date later than 2021-06-04 :no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. @@ -830,7 +830,7 @@ You can learn more about conditional requests by reading [RFC7232](https://datat #### Cache Control One of the more common uses for `ETag` headers is cache control, also referred to a "conditional GET." This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). That is, using the value of the `ETag` , the server can determine if the resource has changed. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. -Implementing this strategy is relatively straightforward. First, you will return an `ETag` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your `ETag`. In these scenarios, when a request is made by the client an `ETag` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The `ETag` value can then be sent in subsequent requests as part of the `if-none-match` header. This tells the service to compare the `ETag` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated `ETag` value in the header. +Implementing this strategy is relatively straightforward. First, you will return an `ETag` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your `ETag`. In these scenarios, when a request is made by the client an `ETag` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The `ETag` value can then be sent in subsequent requests as part of the `If-None-Match` header. This tells the service to compare the `ETag` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated `ETag` value in the header. :ballot_box_with_check: **YOU SHOULD** implement conditional read strategies @@ -839,8 +839,8 @@ When supporting conditional read strategies: | GET Request | Return code | Response | |:------------|:------------|:--------------------------------------------| -| ETag value = if-none-match value | 304 Not Modified | no additional information | -| ETag value != if-none-match value | 200 OK | Response body include the serialized value of the resource (typically JSON) | +| ETag value = `If-None-Match` value | `304-Not Modified` | no additional information | +| ETag value != `If-None-Match` value | `200-OK` | Response body include the serialized value of the resource (typically JSON) | For more control over caching, please refer to the `cache-control` [HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). @@ -854,12 +854,12 @@ When supporting optimistic concurrency: | Operation | Header | Value | ETag check | Return code | Response | |:------------|:--------------|:------|:-----------|:------------|----------------| -| PATCH / PUT | if-none-match | * | check for _any_ version of the resource ('*' is a wildcard used to match anything), if none are found, create the resource. | 200 OK or
201 Created
| Response header MUST include the new `ETag` value. Response body SHOULD include the serialized value of the resource (typically JSON). | -| PATCH / PUT | if-none-match | * | check for _any_ version of the resource, if one is found, fail the operation | 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| -| PATCH / PUT | if-match | value of ETag | value of if-match equals the latest ETag value on the server, confirming that the version of the resource is the most current | 200 OK or
201 Created
| Response header MUST include the new `ETag` value. Response body SHOULD include the serialized value of the resource (typically JSON). | -| PATCH / PUT | if-match | value of ETag | value of if-match header DOES NOT equal the latest ETag value on the server, indicating a change has ocurred since after the client fetched the resource| 412 Precondition Failed | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| -| DELETE | if-none-match | value of ETag | value does NOT match the latest value on the server | 412 Preconditioned Failed | Response body SHOULD be empty.| -| DELETE | if-none-match | value of ETag | value matches the latest value on the server | 204 No Content | Response body SHOULD be empty. | +| PATCH / PUT | `If-None-Match` | * | check for _any_ version of the resource ('*' is a wildcard used to match anything), if none are found, create the resource. | `200-OK` or
`201-Created`
| Response header MUST include the new `ETag` value. Response body SHOULD include the serialized value of the resource (typically JSON). | +| PATCH / PUT | `If-None-Match` | * | check for _any_ version of the resource, if one is found, fail the operation | `412-Precondition Failed` | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| +| PATCH / PUT | `If-Match` | value of ETag | value of `If-Match` equals the latest ETag value on the server, confirming that the version of the resource is the most current | `200-OK` or
`201-Created`
| Response header MUST include the new `ETag` value. Response body SHOULD include the serialized value of the resource (typically JSON). | +| PATCH / PUT | `If-Match` | value of ETag | value of `If-Match` header DOES NOT equal the latest ETag value on the server, indicating a change has ocurred since after the client fetched the resource| `412-Precondition Failed` | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| +| DELETE | `If-None-Match` | value of ETag | value does NOT match the latest value on the server | `412-Preconditioned Failed` | Response body SHOULD be empty.| +| DELETE | `If-None-Match` | value of ETag | value matches the latest value on the server | `204-No Content` | Response body SHOULD be empty. | #### Computing ETags The strategy that you use to compute the `ETag` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the `ETag`. Another common strategy for determining the value of an `ETag` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. From 11835a57d7b7732e75925a1a09ffd7fb4cf16fc3 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 5 Aug 2021 17:44:54 -0500 Subject: [PATCH 150/729] Clarify URL chars and LRO guidance --- azure/Guidelines.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 20d91280..f7642543 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -70,18 +70,19 @@ Where: :white_check_mark: **DO** return `414-URI Too Long` if a URL exceeds 2083 characters -:white_check_mark: **DO** treat URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request **MUST** fail with a `404-Not found` HTTP return code. +:white_check_mark: **DO** treat service-defined URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request **MUST** fail with a `404-Not found` HTTP return code. -:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) +Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' -:ballot_box_with_check: **YOU SHOULD** limit your URL's path characters to `0-9 A-Z a-z - . _ ~ :` +:white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a JSON response body -:heavy_check_mark: **YOU MAY** use these other characters in the URL path but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` +:ballot_box_with_check: **YOU SHOULD** restrict the characters in service-defined path segments to `0-9 A-Z a-z - . _ ~`, with `:` allowed only as described below to designate an action operation. +:ballot_box_with_check: **YOU SHOULD** restrict the characters allowed in path parameters to `0-9 A-Z a-z - . _ ~` (do not allow `:`). -Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' +:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) -:white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a JSON response body +:heavy_check_mark: **YOU MAY** use these other characters in the URL path but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` #### Direct Endpoint URLs @@ -232,6 +233,8 @@ In addition to the above, a field may be "required" or "optional". A required fi :white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. +:white_check_mark: **DO** use camel case for all JSON field names. + :white_check_mark: **DO** treat JSON field names with case-sensitivity. :white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions but avoid if at all possible. @@ -737,7 +740,10 @@ The ability to retry failed requests for which a client never received a respons ### Long Running Operations & Jobs -The Microsoft REST API guidelines for Long Running Operations are an updated, clarified and simplified version of the Asynchronous Operations guidelines from the 2.1 version of the Azure API guidelines. Unfortunately, to generalize to the whole of Microsoft and not just Azure, the HEADER used in the operation was renamed from `Azure-AsyncOperation` to `Operation-Location`. +Azure generally follows the [Microsoft REST API guidelines for Long running operations](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#13-long-running-operations). Follow the Microsoft guidelines with the modifications / extensions given here when designing / implementing long running operations. + +Previous Azure guidelines specified "Azure-AsyncOperation" as the name of the response header containing the operation URL, +while the Microsoft guidelines use the name "Operation-Location". :white_check_mark: **DO** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. From ea3c75dc93e6a14b9ab560ca92bf810b9e2498f5 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 5 Aug 2021 21:15:00 -0500 Subject: [PATCH 151/729] 200-OK response for action operations --- azure/Guidelines.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index f7642543..8fc19e52 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -424,6 +424,8 @@ Note: To avoid potential collision of actions and resource ids, you should disal :white_check_mark: **DO** support the Repeatability-Request-ID & Repeatability-First-Sent request headers if the action needs to be idempotent if retries occur. +:white_check_mark: **DO** return a `200-OK` when the action completes synchronously and successfully. + :ballot_box_with_check: **YOU SHOULD** use a verb to name your action. From fbcbfa740d523418b8215f97c78be727acc5313d Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 6 Aug 2021 09:59:08 -0500 Subject: [PATCH 152/729] More markdown styling --- azure/.markdownlint.json | 16 ++++++ azure/ConsiderationsForServiceDesign.md | 22 ++++---- azure/Guidelines.md | 71 +++++++++++++++---------- 3 files changed, 71 insertions(+), 38 deletions(-) create mode 100644 azure/.markdownlint.json diff --git a/azure/.markdownlint.json b/azure/.markdownlint.json new file mode 100644 index 00000000..13bfb11f --- /dev/null +++ b/azure/.markdownlint.json @@ -0,0 +1,16 @@ +{ + "default": true, + "MD012": { + "maximum": 2 + }, + "MD013": { + "line_length": 500 + }, + "MD022": false, + "MD031": false, + "MD032": false, + "MD033": { + "allowed_elements": [ "sup" ] + }, + "MD036": false +} \ No newline at end of file diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index a19ae389..b10c3001 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -1,7 +1,9 @@ ## Considerations for Service Design + Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, _especially over time_. -This document provides Microsoft teams building Azure services with a set of guidelines that help service teams build great APIs. The guidelines create APIs that are approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. For developers, a well defined and constructed API enables them to build fault-tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation enabling a broad audience of developers across multiple languages. +This document provides Microsoft teams building Azure services with a set of guidelines that help service teams build great APIs. The guidelines create APIs that are approachable, sustainable, and consistent across the Azure platform. We do this by applying a common set of patterns and web standards to the design and development of the API. +For developers, a well defined and constructed API enables them to build fault-tolerant applications that are easy to maintain, support, and grow. For Azure service teams, the API is often the source of code generation enabling a broad audience of developers across multiple languages. Azure Service teams should engage the Azure HTTP/REST Stewardship Board early in the development lifecycle for guidance, discussion, and review of their API. In addition, it is good practice to perform a security review, especially if you are concerned about PII leakage, compliance with GDPR, or any other considerations relative to your situation. @@ -17,7 +19,8 @@ It is critically important to design your service to avoid disrupting users as t *Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review.* A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used to provision and control the operational state of resources. -A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful for provisioning/control and applications. In this case, the operation can appear in both APIs. Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on [the conventions of the Azure ARM](https://github.com/Azure/azure-resource-manager-rpc). +A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful for provisioning/control and applications. In this case, the operation can appear in both APIs. +Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on [the conventions of the Azure ARM](https://github.com/Azure/azure-resource-manager-rpc). ### Start with the Developer Experience @@ -30,12 +33,12 @@ Follow these practices to create clear names for your abstractions: - Pick one word/term out of a set of synonyms and stick to it. It is extremely difficult to create an elegant API that works well on top of a poorly designed service; the service team and customers will live with this pain for years to come. So, the service team should empathize with customers by: - - Building apps that consume the API - - Hold reviews and share what is learned with your team - - Get customer feedback from API previews - - Thinking about the code that a customer writes both before and after an HTTP operation - - Initializing and reading from the data structures your service requires - - Thinking about which errors are recoverable at runtime as opposed to indicating a bug in the customer code that must be fixed +- Building apps that consume the API +- Hold reviews and share what is learned with your team +- Get customer feedback from API previews +- Thinking about the code that a customer writes both before and after an HTTP operation +- Initializing and reading from the data structures your service requires +- Thinking about which errors are recoverable at runtime as opposed to indicating a bug in the customer code that must be fixed The whole purpose of a preview to address feedback by improving abstractions, naming, relationships, API operations, and so on. It is OK to make breaking changes during a preview to improve the experience now so that it is sustainable long term. @@ -117,4 +120,5 @@ When engaging with the API REST Stewardship board, your working sessions will ge - Well formed - Do your services adhere to REST and Azure standards, e.g. proper return codes, use of headers. - Durable - Your APIs will grow and change over time and leveraging the common patterns described in this document will help you minimize your tech debt and move fast with confidence. -It was once said that "all roads lead to Rome." For cloud services, the equivalent might be that "all 'roads' start with your API." That could not be more true than at Microsoft, where client libraries, documentation, and many other artifacts all originate from the fundamental way you choose to expose your service. With careful consideration at the outset of your development effort, the architectural stewardship of the API board, and the thoughtful application of these guidelines, you will be able to produce a consistent, well formed API that will delight our customers. +It was once said that "all roads lead to Rome." For cloud services, the equivalent might be that "all 'roads' start with your API." That could not be more true than at Microsoft, where client libraries, documentation, and many other artifacts all originate from the fundamental way you choose to expose your service. +With careful consideration at the outset of your development effort, the architectural stewardship of the API board, and the thoughtful application of these guidelines, you will be able to produce a consistent, well formed API that will delight our customers. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 8fc19e52..b6314499 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -42,10 +42,10 @@ The Microsoft Azure Cloud platform exposes its APIs through the core building bl ### HTTP Azure services must adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231). This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: -* [Uniform Resource Locators (URLs)](URLS) -* HTTP Methods -* Request & Response Headers -* Bodies +- [Uniform Resource Locators (URLs)](URLS) +- HTTP Methods +- Request & Response Headers +- Bodies ### Uniform Resource Locators (URLs) @@ -92,14 +92,14 @@ https://-../... ``` Examples: - - Request URL: `https://blobstore.azure.net/contoso.com/account1/container1/blob2` - - Response header ([RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4)): `content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2` - - GUID format: `https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2` +- Request URL: `https://blobstore.azure.net/contoso.com/account1/container1/blob2` +- Response header ([RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4)): `content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2` +- GUID format: `https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2` :white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for `` or always a single verified domain. :heavy_check_mark: **YOU MAY** use URLs as values -``` +```http https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy ``` @@ -198,12 +198,14 @@ retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://da :no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. #### Additional References -* [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) -* [Standard HTTP Headers](https://httpwg.org/specs/rfc7231.html#header.field.registration) -* [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) +- [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) +- [Standard HTTP Headers](https://httpwg.org/specs/rfc7231.html#header.field.registration) +- [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) ### REpresentational State Transfer (REST) -REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. See [this article in the Azure Architecture Center](https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design) for a more detailed discussion of REST API design patterns. +REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. +These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. +There are patterns, later in these guidelines, that describe how to invoke behavior on your service. See [this article in the Azure Architecture Center](https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design) for a more detailed discussion of REST API design patterns. When designing your service, it is important to optimize for the developer using your API. @@ -229,7 +231,8 @@ Field Mutability | Service Request's behavior for this field **Update** | Service honors field when creating or updating a resource **Read** | Service returns this field in a response. If the client passed a read-only field, the service **MUST** fail the request unless the passed-in value matches the resource's current value -In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically _not_ become a nullable field in a SDK's data structure. This allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. +In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically _not_ become a nullable field in a SDK's data structure. This allows customers to write code without performing a null-check. +Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. :white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. @@ -274,8 +277,8 @@ PUT | Overwrite resource entirely using Create/Update fields | `200-OK` #### Handling Errors There are 2 kinds of errors: - - An error where you expect customer code to gracefully recover at runtime - - An error indicating a bug in customer code that is unlikely to be recoverable at runtime; the customer must just fix their code +- An error where you expect customer code to gracefully recover at runtime +- An error indicating a bug in customer code that is unlikely to be recoverable at runtime; the customer must just fix their code :white_check_mark: **DO** return error an `x-ms-error-code` response header with a string value indicating what went wrong. @@ -438,7 +441,7 @@ Note: To avoid potential collision of actions and resource ids, you should disal :heavy_check_mark: **YOU MAY** expose an operation that lists your resources by supporting a GET method with a URL to a resource-collection (as opposed to a resource-id). **Example Response Body** -``` +```json { "value": [ { "id": "Item 01", "etag": "0xabc", "price": 99.95, "sizes": null }, @@ -689,7 +692,8 @@ While removing a value from an enum is a breaking change, adding value to an enu Simpler clients may be hardcoded to a single version of a service. Since Azure services offer each version for a well-known period of time, a client that’s regularly maintained can be always operational without further complexity as long as during regular maintenance the client is moved forward to new versions in advance of older ones being retired. -API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. +API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). +In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. :white_check_mark: **DO** support API version discovery, including @@ -725,7 +729,8 @@ api-deprecated-versions: 2009-04,1.0 Content-Length: 0 ``` -Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. +Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. +In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. ### Repeatability of requests @@ -753,9 +758,9 @@ while the Microsoft guidelines use the name "Operation-Location". :white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. - ### Bring your own Storage -When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS will provide the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders, nor do they have extensions. +When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. +While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS will provide the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders, nor do they have extensions. :white_check_mark: **DO** use Azure Bring Your Own Storage. @@ -764,7 +769,8 @@ When implementing your service, it is very common to store and retrieve data and :no_entry: **DO NOT** require a fresh container per operation #### Authentication -How you secure and protect the data and files that your service uses will not only affect how consumable your API is, but also, how quickly you can evolve and adapt it. Implementing Role Based Access Control [RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) is the recommended approach. It is important to recognize that any roles defined in RBAC essentially become part of your API contract. For example, changing a role's permissions, e.g. restricting access, could effectively cause existing clients to break, as they may no longer have access to necessary resources. +How you secure and protect the data and files that your service uses will not only affect how consumable your API is, but also, how quickly you can evolve and adapt it. Implementing Role Based Access Control [RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) is the recommended approach. +It is important to recognize that any roles defined in RBAC essentially become part of your API contract. For example, changing a role's permissions, e.g. restricting access, could effectively cause existing clients to break, as they may no longer have access to necessary resources. :white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions. @@ -827,7 +833,9 @@ For each of the "output" sections the following apply: :white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of `write` permissions ### Conditional Requests -When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an `ETag` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. An `ETag` is always set by the service and will enable you to _conditionally_ control how your service responds to requests, enabling you to provide predictable updates and more efficient access. +When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. +Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an `ETag` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. +An `ETag` is always set by the service and will enable you to _conditionally_ control how your service responds to requests, enabling you to provide predictable updates and more efficient access. :ballot_box_with_check: **YOU SHOULD** return an `ETag` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). @@ -838,7 +846,9 @@ You can learn more about conditional requests by reading [RFC7232](https://datat #### Cache Control One of the more common uses for `ETag` headers is cache control, also referred to a "conditional GET." This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). That is, using the value of the `ETag` , the server can determine if the resource has changed. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. -Implementing this strategy is relatively straightforward. First, you will return an `ETag` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your `ETag`. In these scenarios, when a request is made by the client an `ETag` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The `ETag` value can then be sent in subsequent requests as part of the `If-None-Match` header. This tells the service to compare the `ETag` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated `ETag` value in the header. +Implementing this strategy is relatively straightforward. First, you will return an `ETag` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your `ETag`. +In these scenarios, when a request is made by the client an `ETag` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The `ETag` value can then be sent in subsequent requests as part of the `If-None-Match` header. +This tells the service to compare the `ETag` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated `ETag` value in the header. :ballot_box_with_check: **YOU SHOULD** implement conditional read strategies @@ -853,7 +863,8 @@ When supporting conditional read strategies: For more control over caching, please refer to the `cache-control` [HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). #### Optimistic Concurrency -An `ETag` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of `ETags` and the [HTTP Request / Response Pattern](#http-request--response-pattern). +An `ETag` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. +Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of `ETags` and the [HTTP Request / Response Pattern](#http-request--response-pattern). :warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. @@ -886,12 +897,14 @@ Client libraries are required to send telemetry and distributed tracing informat :no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. #### Additional References -* [Azure SDK client guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) -* [Azure SDK User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) -* [Azure SDK Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) -* [Open Telemetry](https://opentelemetry.io/) +- [Azure SDK client guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) +- [Azure SDK User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) +- [Azure SDK Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) +- [Open Telemetry](https://opentelemetry.io/) ## Final thoughts These guidelines describe the upfront design considerations, technology building blocks, and common patterns that Azure teams encounter when building an API for their service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. -The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. For more information on how to part with the Stewardship board, please refer to [Considerations for Service Design](.\ConsiderationsForServiceDesign.md). +The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. +Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. +These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. For more information on how to part with the Stewardship board, please refer to [Considerations for Service Design](.\ConsiderationsForServiceDesign.md). From fe46e9334814ade6e97bcba96c1915ace24b3d81 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 6 Aug 2021 11:27:25 -0500 Subject: [PATCH 153/729] Last round of updates --- azure/Guidelines.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index b6314499..cd82a4a7 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -76,9 +76,9 @@ Some customer-provided path segment values may be compared case-insensitivity if :white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a JSON response body -:ballot_box_with_check: **YOU SHOULD** restrict the characters in service-defined path segments to `0-9 A-Z a-z - . _ ~`, with `:` allowed only as described below to designate an action operation. +:white_check_mark: **DO** restrict the characters in service-defined path segments to `0-9 A-Z a-z - . _ ~`, with `:` allowed only as described below to designate an action operation. -:ballot_box_with_check: **YOU SHOULD** restrict the characters allowed in path parameters to `0-9 A-Z a-z - . _ ~` (do not allow `:`). +:ballot_box_with_check: **YOU SHOULD** restrict the characters allowed in user-specified path segements (i.e. path parameters values) to `0-9 A-Z a-z - . _ ~` (do not allow `:`). :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) @@ -240,7 +240,7 @@ Because of this, required fields can only be introduced in the 1st version of a :white_check_mark: **DO** treat JSON field names with case-sensitivity. -:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions but avoid if at all possible. +:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions (e.g. GUIDs) but avoid if at all possible. :white_check_mark: **DO** use GET for resource retrieval and return JSON in the response body From c0343cfc318a17a205f017ebaee247ca4ff30fe4 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 6 Aug 2021 13:55:48 -0500 Subject: [PATCH 154/729] Update history --- azure/ConsiderationsForServiceDesign.md | 7 +++++++ azure/Guidelines.md | 12 +++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index b10c3001..0618deaa 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -1,4 +1,11 @@ ## Considerations for Service Design +### History + +| Date | Notes | +| ----------- | -------------------------------------------------------------- | +| 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | + +### Introduction Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, _especially over time_. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index cd82a4a7..64969cf6 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,11 +1,11 @@ # Microsoft Azure REST API Guidelines ## History -| Date | Version | Notes | -| ----------- | ------- | --------------------------------------------------- | -| 2021-Jul-02 | vNext-RC1 | Updated Azure REST Guidelines. | -| 2020-Jul-31 | v3.2 | Added service advice for initial versions | -| 2020-Mar-31 | v3.1 | 1st public release of the Azure REST API Guidelines | +| Date | Notes | +| ----------- | -------------------------------------------------------------- | +| 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | +| 2020-Jul-31 | Added service advice for initial versions | +| 2020-Mar-31 | 1st public release of the Azure REST API Guidelines | ## Introduction @@ -20,6 +20,8 @@ These guidelines offer prescriptive guidance that Azure service teams MUST follo Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. +See the [Considerations for Service Design](./ConsiderationsForServiceDesign.md) for an introduction to the topic of API design for Azure services. + *NOTE: For an existing GA'd service, don't change/break its existing API; instead, leverage these concepts for future APIs while prioritizing consistency within your existing service.* ### Prescriptive Guidance From c8ee97b6f2e61c5e14379f6fe020c8d432b777db Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Sun, 8 Aug 2021 09:45:29 -0700 Subject: [PATCH 155/729] Simplify BYOS section --- azure/Guidelines.md | 71 ++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 64969cf6..8faa65e7 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -760,79 +760,64 @@ while the Microsoft guidelines use the name "Operation-Location". :white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. -### Bring your own Storage -When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. -While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS will provide the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders, nor do they have extensions. +### Bring your own Storage (BYOS) +Many services need to store and retrieve data files. For this scenario, teh service should not implement its own +storage mechanism and should instead leverage the existing Azure Storage service. When doing this, the customer +"owns" the storage account and just tells your service to use it. Colloquially, we call this Bring Your Own Storage as the customer is bringing their storage account to another service. BYOS provides significant benefits to service implementors: security, performance, uptime, etc. And, of course, most Azure customers are already with the Azure Storage service. -:white_check_mark: **DO** use Azure Bring Your Own Storage. +While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS provides the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders. -:white_check_mark: **DO** use a blob prefix +:white_check_mark: **DO** use the Bring Your Own Storage pattern + +:white_check_mark: **DO** use a blob prefix for logical folders :no_entry: **DO NOT** require a fresh container per operation -#### Authentication -How you secure and protect the data and files that your service uses will not only affect how consumable your API is, but also, how quickly you can evolve and adapt it. Implementing Role Based Access Control [RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) is the recommended approach. -It is important to recognize that any roles defined in RBAC essentially become part of your API contract. For example, changing a role's permissions, e.g. restricting access, could effectively cause existing clients to break, as they may no longer have access to necessary resources. +:white_check_mark: **DO** use managed identity and Role Based Access Control ([RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview)) as the mechanism allowing customers to grant permission to their Storage account to your service. :white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions. :white_check_mark: **DO** Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. -##### Handling 'downstream' errors -It is not uncommon to rely on other services, e.g. storage, when implementing your service. Inevitably, the services you depend on will fail. In these situations, you can include the downstream error code and text in the inner-error of the response body. This provides a consistent pattern for handling errors in the services you depend upon. - -:white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. - -#### Working with files -Generally speaking, there are two patterns that you will encounter when working with files; single file access, and file collections. - -##### Single file access -Desiging an API for accessing a single file, depending on your scenario, is relatively straight forward. - -:heavy_check_mark: **YOU MAY** use a Shared Access Signature [SAS](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. +:heavy_check_mark: **YOU MAY** use Shared Access Signatures [SAS](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) to provide access to Storage objects. :ballot_box_with_check: **YOU SHOULD** if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text. -:ballot_box_with_check: **YOU SHOULD** support managed identity using Azure Storage by default (if using Azure services). +:white_check_mark: **DO** return an HTTP status code representing the result of your service operation's behavior. If the operation uses Storage internally, this an implementation detail from the client's perspective of the operation but may happen due to a missing Storage object or insufficient permissions. If the internal Storage operation fails, include the Storage error information in the 'inner-error' section of your operation's response body to help the client know that they may have configured their Storage improperly. -###### File Versioning -Depending on your requirements, there are scenarios where users of your service will require a specific version of a file. For example, you may need to keep track of configuration changes over time to be able to rollback to a previous state. In these scenarios, you will need to provide a mechanism for accessing a specific version. +:white_check_mark: **DO** allow the customer to specify a URL path to a single Storage object if your service requires access to a single file. -:white_check_mark: **DO** Enable the customer to provide an ETag to specify a specific version of a file. -##### File Collections -When your users need to work with multiple files, for example a document translation service, it will be important to provide them access to the collection, and its contents, in a consistent manner. Because there is no industry standard for working with containers, these guidelines will recommend that you leverage Azure Storage. Following the guidelines above, you also want to ensure that you don't expose file system constructs, e.g. folders, and instead use storage constructs, e.g. blob prefixes. +:heavy_check_mark: **YOU MAY** allow the customer to provide a [last-modified](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2) timestamp (in RFC1123 format) for read-only files. This allows the client to specify exactly which version of the files your service should use. When reading a file, your service passes this timestamp to Azure Storage using the [if-unmodified-since](https://datatracker.ietf.org/doc/html/rfc7232#section-3.4) request header. If the Storage operation fails with 412, the Storage object was modified and your service operation should return an appropriate 4xx status code and return the Storage error in your operation's 'inner-error' (see guideline above). -:white_check_mark: **DO** When using a Shared Access Signature (SAS), ensure this is assigned to the container and that the permissions apply to the content as well. +:white_check_mark: **DO** allow the customer to specify a URL path to a logical directory (via prefix and delimiter) if your service requires access to multiple files (within this directory). For more information, see [List Blobs API](https://docs.microsoft.com/en-us/rest/api/storageservices/list-blobs) -:white_check_mark: **DO** When using managed identity, ensure the customer has given the proper permissions to access the file container to the service. +A common pattern when working with multiple files is for your service to receive requests that contain the location(s) of files to process ("input") and a location(s) to place any files that result from processing ("output"). Note: the terms "input" and "output" are just examples; use terms more appropriate to your service's domain. -A common pattern when working with multiple files is for your service to receive requests that contain the location(s) of files to process, e.g. "input" and a location(s) to place the any files that result from processing, e.g. "output." (Note: the terms "input" and "output" are just examples and terms more relevant to the service domain are more appropriate.) - -For example, in a request payload may look similar to the following: +For example, a service's request body to configure BYOS may look like this: ```json { -"input":{ + "input":{ "location": "https://mycompany.blob.core.windows.net/documents/english/?", + "delimiter": "/", + "lastModified": "Wed, 21 Oct 2015 07:28:00 GMT" + }, + "output":{ + "location": "https://mycompany.blob.core.windows.net/documents/spanish/?", "delimiter":"/" - }, -"output":{ - "location": "https://mycompany.blob.core.windows.net/documents/spanglish/?", - "delimiter":"/" - } + } } ``` -Note: How the service gets the request body is outside the purview of these guidelines. -Depending on the requirements of the service, there can be any number of "input" and "output" sections, including none. However, for each of the "input" sections the following apply: +Depending on the requirements of the service, there can be any number of "input" and "output" sections, including none. -:white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter." +:white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter". For "location", the customer must pass a URL to a blob prefix which represents a directory. For "delimiter", the customer must specify the delimiter character they desire to use in the location URL; typically "/" or "\". -:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of `listing` and `read` permissions. +:heavy_check_mark: **YOU MAY** support the "lastModified" field for input directories (see guideline above). -For each of the "output" sections the following apply: +:white_check_mark: **DO** support a "location" URL with a container-scoped SAS that has a minimum of `listing` and `read` permissions for input directories. -:white_check_mark: **DO** use a URL to a blob prefix with a container scoped SAS on the end with a minimum of `write` permissions +:white_check_mark: **DO** support a "location" URL with a container-scoped SAS that has a minimum of `write` permissions for output directories. ### Conditional Requests When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. From 4ea5ad771d6102159aa35cf9a13aa9d4286ff158 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Mon, 9 Aug 2021 10:36:55 -0700 Subject: [PATCH 156/729] Update azure/Guidelines.md Co-authored-by: Johan Stenberg (MSFT) --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 8faa65e7..29217cd8 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -761,7 +761,7 @@ while the Microsoft guidelines use the name "Operation-Location". :white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. ### Bring your own Storage (BYOS) -Many services need to store and retrieve data files. For this scenario, teh service should not implement its own +Many services need to store and retrieve data files. For this scenario, the service should not implement its own storage mechanism and should instead leverage the existing Azure Storage service. When doing this, the customer "owns" the storage account and just tells your service to use it. Colloquially, we call this Bring Your Own Storage as the customer is bringing their storage account to another service. BYOS provides significant benefits to service implementors: security, performance, uptime, etc. And, of course, most Azure customers are already with the Azure Storage service. From 7db5a22b61813aef97ed700454e998f5408b3cfb Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 3 Sep 2021 07:49:34 -0500 Subject: [PATCH 157/729] Clarify error response structure and casing for acronyms --- azure/Guidelines.md | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 64969cf6..1cce6058 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -238,7 +238,7 @@ Because of this, required fields can only be introduced in the 1st version of a :white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. -:white_check_mark: **DO** use camel case for all JSON field names. +:white_check_mark: **DO** use camel case for all JSON field names. Do not upper-case acronyms; use camel case. :white_check_mark: **DO** treat JSON field names with case-sensitivity. @@ -282,17 +282,42 @@ There are 2 kinds of errors: - An error where you expect customer code to gracefully recover at runtime - An error indicating a bug in customer code that is unlikely to be recoverable at runtime; the customer must just fix their code -:white_check_mark: **DO** return error an `x-ms-error-code` response header with a string value indicating what went wrong. +:white_check_mark: **DO** return an `x-ms-error-code` response header with a string error code indicating what went wrong. *NOTE: Error code values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future.* :white_check_mark: **DO** carefully craft `x-ms-error-code` string values for errors that are recoverable at runtime. -:white_check_mark: **DO** ensure that the top-level `code` field's value is identical to the `x-ms-error-code` header's value (see example body below). +:white_check_mark: **DO** ensure that the top-level `code` field's value is identical to the `x-ms-error-code` header's value. :white_check_mark: **DO** document the service's error code strings; they are part of the API contract. -:white_check_mark: **DO** provide a response body as follows (example): +:white_check_mark: **DO** provide a response body with the following structure: + +**ErrorResponse** : Object + +Property | Type | Required | Description +-------- | ---- | -------- | ----------- +`error` | ErrorDetail | ✔ | The error object. + +**ErrorDetail** : Object + +Property | Type | Required | Description +-------- | ---- | -------- | ----------- +`code` | String | ✔ | One of a server-defined set of error codes. +`message` | String | ✔ | A human-readable representation of the error. +`target` | String | | The target of the error. +`details` | ErrorDetail[] | | An array of details about specific errors that led to this reported error. +`innererror` | InnerError | | An object containing more specific information than the current object about the error. + +**InnerError** : Object + +Property | Type | Required | Description +-------- | ---- | -------- | ----------- +`code` | String | | A more specific error code than was provided by the containing error. +`innererror` | InnerError | | An object containing more specific information than the current object about the error. + +Example: ```json { "error": { From f930569f693f0486c23aa91ee36c062514797fd9 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 3 Sep 2021 08:46:39 -0500 Subject: [PATCH 158/729] Clarify status codes for POST --- azure/Guidelines.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 1cce6058..2c902741 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -127,6 +127,7 @@ Method | Description | Response Status Code PATCH | Create/Modify the resource with JSON Merge Patch | `200-OK`, `201-Created` PUT | Create/Replace the _whole_ resource | `200-OK`, `201-Created` POST | Create new resource (ID set by service) | `201-Created` with URL of created resource +POST | Action | `200-OK`, `204-No Content` GET | Read (i.e. list) a resource collection | `200-OK` GET | Read the resource | `200-OK` DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` @@ -297,13 +298,13 @@ There are 2 kinds of errors: **ErrorResponse** : Object Property | Type | Required | Description --------- | ---- | -------- | ----------- +-------- | ---- | :------: | ----------- `error` | ErrorDetail | ✔ | The error object. **ErrorDetail** : Object Property | Type | Required | Description --------- | ---- | -------- | ----------- +-------- | ---- | :------: | ----------- `code` | String | ✔ | One of a server-defined set of error codes. `message` | String | ✔ | A human-readable representation of the error. `target` | String | | The target of the error. @@ -313,7 +314,7 @@ Property | Type | Required | Description **InnerError** : Object Property | Type | Required | Description --------- | ---- | -------- | ----------- +-------- | ---- | :------: | ----------- `code` | String | | A more specific error code than was provided by the containing error. `innererror` | InnerError | | An object containing more specific information than the current object about the error. From c1b228ffedc84145677f4da83b474ee778883086 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 3 Sep 2021 12:09:57 -0400 Subject: [PATCH 159/729] Update azure/Guidelines.md --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 2c902741..959faa41 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -127,7 +127,7 @@ Method | Description | Response Status Code PATCH | Create/Modify the resource with JSON Merge Patch | `200-OK`, `201-Created` PUT | Create/Replace the _whole_ resource | `200-OK`, `201-Created` POST | Create new resource (ID set by service) | `201-Created` with URL of created resource -POST | Action | `200-OK`, `204-No Content` +POST | Action | `200-OK`, `204-No Content` (only when nothing returned in response body) GET | Read (i.e. list) a resource collection | `200-OK` GET | Read the resource | `200-OK` DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` From fc61de11c6efea213c686c734bd45b4c1cbcf8dd Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sat, 11 Sep 2021 08:36:53 -0500 Subject: [PATCH 160/729] Add long-running operations guidance --- azure/ConsiderationsForServiceDesign.md | 114 ++++++++++++++++++++++-- azure/Guidelines.md | 72 +++++++++++++-- azure/relo.drawio | 1 + azure/relo.jpg | Bin 0 -> 20362 bytes azure/statmon.drawio | 1 + azure/statmon.jpg | Bin 0 -> 26379 bytes 6 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 azure/relo.drawio create mode 100644 azure/relo.jpg create mode 100644 azure/statmon.drawio create mode 100644 azure/statmon.jpg diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 0618deaa..600805fa 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -1,11 +1,13 @@ -## Considerations for Service Design -### History +# Considerations for Service Design + +## History | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2021-Sep-11 | Add long-running operations guidance | | 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | -### Introduction +## Introduction Great APIs make your service usable to customers. They are intuitive, naturally reflecting and communicating the underlying model and its behavior. They lend themselves easily to client library implementations in multiple programming languages. And they don't "get in the way" of the developer, by remaining stable and predictable, _especially over time_. @@ -22,7 +24,7 @@ It is critically important to design your service to avoid disrupting users as t :white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library **without requiring code changes** -### Azure Management Plane vs Data Plane +## Azure Management Plane vs Data Plane *Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review.* A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used to provision and control the operational state of resources. @@ -30,7 +32,7 @@ A **data plane** API is used by developers to implement applications. Occasional Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on [the conventions of the Azure ARM](https://github.com/Azure/azure-resource-manager-rpc). -### Start with the Developer Experience +## Start with the Developer Experience A great API starts with a well thought out and designed service. Your service should define simple/understandable abstractions with each given a clear name that you use consistently throughout your API and documentation. There must also be an unambiguous relationship between these abstractions. Follow these practices to create clear names for your abstractions: @@ -49,7 +51,7 @@ It is extremely difficult to create an elegant API that works well on top of a p The whole purpose of a preview to address feedback by improving abstractions, naming, relationships, API operations, and so on. It is OK to make breaking changes during a preview to improve the experience now so that it is sustainable long term. -### Focus on Hero Scenarios +## Focus on Hero Scenarios It is important to realize that writing an API is, in many cases, the easiest part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, client libraries, examples, blog posts, videos, and supporting customers in perpetuity. In fact, implementing an API is of miniscule cost compared to all the other downstream activities. *For this reason, it is **much better** to ship with fewer features and only add new features over time as required by customers.* @@ -71,7 +73,7 @@ Understanding how your service is used and defining its model and interaction pa :white_check_mark: **DO** create an [OpenAPI Definition](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. -### Use Previews to Iterate +## Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. :ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. @@ -84,7 +86,7 @@ Before releasing your API plan to invest significant design effort, get customer :ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. -### Avoid Surprises +## Avoid Surprises A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. One common area of friction for developers is _polymorphism_ -- where a value may have any of several types or structures. @@ -110,13 +112,107 @@ Another important design pattern for avoiding surprises is idempotency. An opera HTTP requires certain operations like GET, PUT, and DELETE to be idempotent, but for cloud services it is important to make _all_ operations idempotent so that clients can use retry in failure scenarios without risk of unintended consequences. See the [HTTP Request / Response Pattern section of the Guidelines](./Guidelines.md#http-request--response-pattern) for detailed guidance on making operations idempotent. -### Design for Change Resiliency +## Design for Change Resiliency As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. :ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. :ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. +## Long-Running Operations + +Long-running operations are an API design pattern that should be used when the processing of +an operation may take a significant amount of time -- longer than a client will want to block +waiting for the result. +Azure allows for two forms of this design pattern: resource-based long-running operations (RELO), +which is the preferred pattern, and long-running operations with a status monitor. + +In both patterns, the processing of the operation is initiated by one API call and the client +obtains the results of the operation from a subsequent API call. +Here we illustrate the sequence of API calls involved in each of these patterns. + +### Resource-based long-running operations + +In the RELO pattern, the resource that is the target of the operation contains a `status` field +that holds the status of an outstanding or last completed operation. +This means that the client can use a standard "get" operation on the resource to determine the +status of an operation it initiated. The flow looks like this: + + +

+ The RELO flow +

+ + +1. The client sends the initial request to the resource to initiate the long-running operation. +This initial request could be a PUT, PATCH, POST, or DELETE method. + +2. The resource validates the request and initiates the operation processing. +It sends a response to client with a `200-OK` HTTP status code (or `201-Created` if the operation +is a create operation) and a representation of the resource where the `status` field is set +to a value indicating that the operation processing has been started. + +3. The client then issues a GET request to the resource to determine if the operation processing +has completed. + +4. The resource responds with a representation of the resource. While the operation is still being +processed, the status field will contain a "non-terminal" value, like `Processing`. + +5. After the operation processing has completed, a GET request from the client will receive a response +where the status field contains a "terminal" value -- `Succeeded`, `Failed`, or `Canceled` -- +that indicates the result of the operation. + +A resource may support multiple outstanding RELO operations, where the status field of the resource +indicates the combined status of the outstanding operations. +If a new operation request is received when there is already a long-running operation in progress for a resource, +the service should reject the operation if it is inconsistent with one already in progress. +However, if the new operation is redundant or not inconsistent with the one in progress, +for example a "reboot" operation on a VM that is in the process of rebooting, then the service should +accept the request. The status field of the resource should then report the completion status of _both_ +operations. + +Note: The RELO pattern should not be used in cases where the completion status of individual operations +may be important to users, as opposed to simply learning that an operation of the type they requested +(e.g. create a resource with a specific name) has successfully completed. + +### Long-running operations with status monitor + +In the LRO with status monitor pattern, the status and results of the operation are encapsulated into +a status monitor resource that is distinct from the target resource and specific to the individual +operation request. Here's what the status monitor LRO pattern looks like: + + +

+ The status monitor LRO flow +

+ + +1. The client sends the request to initiate the long-running operation. +As in the RELO pattern, the initial request could be a PUT, PATCH, POST, or DELETE method. + +2. The resource validates the request and initiates the operation processing. +It sends a response to the client with a `202-Accepted` HTTP status code. +Included in this response is an `Operation-location` response header with the absolute URL of +status monitor for this specific operation. +The response also includes a `Retry-after` header telling the client a minimum time to wait (in seconds) +before sending a request to the status monitor URL. + +3. After waiting at least the amount of time specified by the previous response's `Retry-after` header, +the client issues a GET request to the status monitor URL. + +4. The status monitor URL responds with information about the operation including its current status, +which should be represented as one of a fixed set of string values in a field named `status`. +If the operation is still being processed, the status field will contain a "non-terminal" value, like `Processing`. + +5. After the operation processing completes, a GET request to status monitor URL returns a response with a status field containing a terminal value -- `Succeeded`, `Failed`, or `Canceled` -- that indicates the result of the operation. +If the status is `Failed`, the status monitor resource must contain an `error` field with a `code` and `message` that describes the failure. +If the status is `Succeeded`, the response may contain additional fields as appropriate, such as results +of the operation processing. + +An important distinction between RELO and status monitor LROs is that there is a unique status monitor for each +status monitor LRO, whereas the status of all RELO operations is combined into the status of the resource. +So status monitor LROs are "one-to-one" with their operation status, whereas RELO-style LROs are "many-to-one". + ## Getting Help: The Azure REST API Stewardship Board The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 959faa41..ea4571d0 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -3,6 +3,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2021-Sep-11 | Add long-running operations guidance | | 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | | 2020-Jul-31 | Added service advice for initial versions | | 2020-Mar-31 | 1st public release of the Azure REST API Guidelines | @@ -132,7 +133,7 @@ GET | Read (i.e. list) a resource collection | `200-OK` GET | Read the resource | `200-OK` DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` -:white_check_mark: **DO** return status code `202-Accepted` and follow the guidance in [Long Running Operations & Jobs](#long-running-operations--jobs) when a PUT, POST, or DELETE method completes asynchronously +:white_check_mark: **DO** return status code `202-Accepted` and follow the guidance in [Long-Running Operations & Jobs](#long-running-operations--jobs) when a PUT, PATCH, POST, or DELETE method completes asynchronously. :white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase @@ -773,18 +774,75 @@ The ability to retry failed requests for which a client never received a respons [OpenAPI Specification]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md -### Long Running Operations & Jobs +### Long-Running Operations & Jobs -Azure generally follows the [Microsoft REST API guidelines for Long running operations](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#13-long-running-operations). Follow the Microsoft guidelines with the modifications / extensions given here when designing / implementing long running operations. +When the processing for an operation may take a significant amount of time to complete, it should be +implemented as a _long-running operation (LRO)_. This allows clients to continue running while the +operation is being processed. The client obtains the outcome of the operation at some later time +through another API call. +See the [Long Running Operations section](./ConsiderationsForServiceDesign.md#long-running-operations) in +Considerations for Service Design for an introduction to the design of long running operations. -Previous Azure guidelines specified "Azure-AsyncOperation" as the name of the response header containing the operation URL, -while the Microsoft guidelines use the name "Operation-Location". +:white_check_mark: **DO** implement an operation as an LRO if the 99th percentile response time is greater than 1s. -:white_check_mark: **DO** support both `Azure-AsyncOperation` and `Operation-Location` HEADERS, even though they are redundant so that existing SDKs and clients will continue to operate. +In rare instances where an operation may take a _very long_ time to complete, e.g. longer than 15 minutes, +it may be better to expose this as a first class resource of the API rather than as an operation on another resource. + +There are two basic patterns that can be used for long-running operations: +1. Resource-based long-running operations (RELO) +2. Long-running operations with status monitor + +:white_check_mark: **DO** use the RELO pattern when the operation is on a resource that contains a "status" property that can be used to obtain the outcome of the operation. + +:ballot_box_with_check: **YOU SHOULD** only use the status monitor LRO pattern when the RELO pattern is not applicable. + +#### Resource-based long-running operations + +Some common situations where the RELO pattern should be used: +1. A "create" operation (PUT, PATCH, or POST) for a resource where the basic structure of the resource is created immediately and includes a status field that indicates when the create has completed, e.g. "provisioning" -> "active". +2. An action operation for a resource where both the initiation of the action and the completion of the action cause a change to the "status" property of the resource. + +:white_check_mark: **DO** return a `200-OK` response, `201-Created` for create operations, from the request that initiates the operation. The response body should contain a representation of the resource that clearly indicates that the operation has been accepted or started. + +:white_check_mark: **DO** support a get method on the resource that returns a representation of the resource including the status field that indicates when the operation has completed. + +:white_check_mark: **DO** define the "status" field of the resource as an enum with all the values it may contain including the "terminal" values "Succeeded", "Failed", and "Canceled". + +:ballot_box_with_check: **YOU SHOULD** use the name `status` for the "status" field of the resource. + +#### Long-running operations with status monitor + +In a long-running operation with status monitor, the client makes a request to initiate the operation processing and receives a URL in the response where it can obtain the operation results. The [HTTP specification](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.3) calls the target of this URL a "status monitor". + +:white_check_mark: **DO** return a `202-Accepted` status code from the request that initiates an LRO with status monitor if the processing of the operation was successfully initiated. + +:white_check_mark: **DO** perform as much validation of the initial request as practical and return an error response immediately when appropriate (without starting the operation). + +:white_check_mark: **DO** return the status monitor URL in the `Operation-Location` response header. + +:white_check_mark: **DO** support the `get` method on the status monitor endpoint that returns a `200-OK` response with a response body that contains the completion status of the operation with sufficient information to diagnose any potential failures. + +:white_check_mark: **DO** include a field in the status monitor resource named `status` indicating the operation's status. This field should be a string with well-defined values. Indicate the terminal state using "Succeeded", "Failed", or "Canceled". + +:white_check_mark: **DO** include a field in the status monitor named `error` to contain error information -- minimally `code` and `message` fields -- when an operation fails. + +:white_check_mark: **DO** retain the status monitor resource for some documented period of time (at least 24 hours) after the operation completes. + +:white_check_mark: **DO** include a `Retry-After` header in the response to the initiating request and requests to the operation-location URL. The value of this header should be an integer number of seconds to wait before making the next request to the operation-location URL. + +:heavy_check_mark: **YOU MAY** support a `get` method on the status monitor collection URL that returns a list of status monitors for all recently initiated operations. + +:warning: **YOU SHOULD NOT** return any other `2xx` status code from the initial request of a status-monitor LRO -- return `202-Accepted` and a status monitor URL even if processing was completed before the initiating request returns. + +:no_entry: **DO NOT** return any data in the response body of a `202-Accepted` response. + +Previous Azure guidelines specified "Azure-AsyncOperation" as the name of the response header containing the status monitor URL. + +:white_check_mark: **DO** return **both** `Azure-AsyncOperation` and `Operation-Location` headers if your service previously returned `Azure-AsyncOperation`, even though they are redundant, so that existing clients will continue to operate. :white_check_mark: **DO** return the same value for **both** headers. -:white_check_mark: **DO** look for **both** HEADERS in client code, preferring the `Operation-Location` version. +:white_check_mark: **DO** look for **both** headers in client code, preferring the `Operation-Location` header. ### Bring your own Storage When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. diff --git a/azure/relo.drawio b/azure/relo.drawio new file mode 100644 index 00000000..4c6c4a61 --- /dev/null +++ b/azure/relo.drawio @@ -0,0 +1 @@ +5VhbV6MwGPw1POqBBGj7aFutD7vas8WjPkaIkCMlbBpsu79+g4RLSKW37e6qL20zhA8yM9+QYsDRfDVhKI2+0wDHBjCDlQHHBgAugOIzB9YFYNv9AggZCQrIqoEZ+YUlaEo0IwFeKBM5pTEnqQr6NEmwzxUMMUaX6rRnGqtXTVGINWDmo1hH70nAowLtO2aNX2MSRuWVLVMemaNysgQWEQrosgHBSwOOGKW8+DVfjXCcc1fyUpx39c7R6sYYTvguJ8AkG16t2MBffzPT5PbeA2hyJsV4RXEmFzy980SN6YU3us6/b2eevH2+Ljl5xYwTQdFFTMJEQE+Uczo34BAnwUXOeY7F1H8RULVqMx9wRl8qHi2BPNOES9EtwchQX5RcJw4UneQSJ5jOMWdrMYHhGHHyqsqGpPphNa86dUqJuAQwpVEtKGWSPrVcUy2xoBnzsTyrSXOrkG1vKcQRCzHXCokfjfXU0JuKeyg60BS99rypQEDuTFHT/IGLtRiOEATxrLgv92eWO3E4ZdTHiwVJwhoznPGXMIGm3QAcZgLNTe1Cf84Ek7u4P/UGmZ0CAm8eQkL5zZmlmWAUEyyv1VSR0SwJcCCFWEaE41mK/PzoUuS5wCI+jzfo5HTplHsDrzqVkkd7lsLTQNK2rKO2CtSoEbPQfF9ahct9ibM14jTKGv5OaCLAYYAWUcVhB1+CJrZ+yNvgvBo+yrlvg/FK9kgxWm/smM4OKXy13RlbO6mhgLNBgBI7NnWBqj/sHZi60FULaQ45ccP1tvsmFL2W7t4y1ZYGPZUVzM1SleFlqpmzay+B/qmaCWqkVI+eE+dQp0bbw+mfxY/zUeOni/D/JGvs/rmjZkTb+Dtv8dqV/nLY6Jv2yeUH2aWXcd3Kqt7R+3UArQPFtLcUOrGYX3q//o4dnN7ezd1W0e0dZgfNV+1Cp965m5+uuS2743G5mwpVyWO7Wyt0ajn1f2L7tfcs832M863Rp+luC7jHtjeE7mF+0IzVLnSwH8SwfpdXTK9fiMLL3w== \ No newline at end of file diff --git a/azure/relo.jpg b/azure/relo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ffaa7cb71383279c582714077dfc3d9c2f98b1ba GIT binary patch literal 20362 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 Zta@0$lqf?`cCEO;Rbltvujj9o{{^+L>>~gG literal 0 HcmV?d00001 diff --git a/azure/statmon.drawio b/azure/statmon.drawio new file mode 100644 index 00000000..aafa1cdb --- /dev/null +++ b/azure/statmon.drawio @@ -0,0 +1 @@ +5Vhrc6IwFP01fHSHEB760Qe1O305xU7bjyxEYIsJG0PV/voNEnnF+traqd12piWH5GLOOTf3igL708WQukl4Q3wUK5rqLxQ4UDTNggb/mwHLHDBUAQQ08nMIlIATvSEBqgJNIx/NahMZITGLkjroEYyRx2qYSymZ16dNSFx/auIGSAIcz41l9DHyWZijbUMt8UsUBeH6yUAVd6buerIAZqHrk3kFgrYC+5QQll9NF30UZ9ytecnXXbxzt/hgFGG2zwKI097Fgna85bWa4LvHseYOW+08yqsbp2LDo4cxjzHqjvuX2f87JxsO7Gt7bIt9sOWanFdEWcS56sZRgDn0izBGpgrsIex3M/IzLCbeC4eK7avZgFHyUhAKODIhmAn1AaemJ+9ObBj5NcHEXoeITBGjSz6Bothl0WtdP1fYICjmFUtHJOKP0FThWGAJgwrDAlP9oVZ+GgFnJKUeEjGq7DfC6lBthq0HYi4NEJMC8YvK7kpoJe4BQnckoS/H4xFH+K+imYoGQSbMXYIoZ4/gFpdtdbGabMZchJ7DXJbO+JobgiNGKL96uL/mdwO2UqyIcp992JY7YYhWlmNcTv0PjNRUXAP6cdYB1o5AH2ed4UPcHo07qZ5oEbx9CiLCbltAsk4/jpB4VlVFSlLsI18IMQ8jhpzE9bK7c14cOBayabxBJ2ObTpk30GKrUuu7oE5URwzn5cFdHM9h5dCG6vva1sg8lDldYk7irGJwTDAHe747CwsStxDGeaLLpywPfhTDZzF3NRgsRJLko+XGlNmaIrmxdltjZypVFDA2CLDG/vXoNuv6w7Zx5GHdMJLkkBNnnFyVh/b4PA7NxTqlGuVOhQer2W6IYMKPKMRm81g24adqu6UQ8w/GY6qKxRVaVdpV7fyTZh1ib0SJh2azCAclpliDfMn3KLjveKeZxbutI2ncOfIgkEpvM9CJzQIls3RHPzlgYz8pnvmVSjBsd75WCTbOvATvnTQdUCfekojfVnkPaGv1HbVx7yLbCAQ061S5dTWnb1cvfeeZQhvc/GbTyZOzoa2Vv998sewy2ubO7AInyq6NHGrfPbs+p3E19Q9qXJuBgHayxnWjH+R6dfaNq6UfqmazcYVQP0XjysN+qrbyl9n9Glcn9TyEsmOz3reesymaL+8Ob0mhdWTZbLakUqCjbcCH5dvofHr5Sh/afwE= \ No newline at end of file diff --git a/azure/statmon.jpg b/azure/statmon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2a7b58c0a8494836122636fcb304614522498396 GIT binary patch literal 26379 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 ~P*^8fV9A~2%YFxbhl2MH39+{QKOjnf?_=tZUHc Date: Mon, 13 Sep 2021 10:39:13 -0400 Subject: [PATCH 161/729] Updated README files to call out Azure guidelines --- README.md | 3 +++ azure/README.md | 11 +++++++++++ 2 files changed, 14 insertions(+) create mode 100644 azure/README.md diff --git a/README.md b/README.md index 87b28610..1a0ca3ec 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ They may additionally create documents specific to their team, adding further gu We publish these guidelines here with the aim of fostering dialogue and learning in the API community at large. We further hope that these guidelines may encourage other organizations to create guidelines that are appropriate for them and in turn, if they are able, to publish theirs. +### Additional guidance for Azure service teams +Azure service teams should reference the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. + [![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) ## Code of Conduct diff --git a/azure/README.md b/azure/README.md new file mode 100644 index 00000000..5fcc0b62 --- /dev/null +++ b/azure/README.md @@ -0,0 +1,11 @@ +# Azure REST API Guidance +When building software components that will be used by developers to build other software, providing APIs that are easy to use, fit to purpose, scalable, maintainable, and consistent across your product can make the difference between success and failure for your software. + +Designing powerful APIs with strong defaults, consistent behavior across related projects, and ease of use for developers arises from putting yourself in the shoes of the person using your interfaces, and taking their concerns to heart. The APIs you ship can have a dramatic long term effect on the health of your software product, and that is why the **REST API Stewardship Board** is here to help! We have published a collection of best practices, REST guidance, and OpenAPI style guidelines to help you create an amazing developer experience. +* [Considerations for Service Design](ConsiderationsForServiceDesign.md) +* [Guidelines](Guidelines.md) + + +You can reach out to use via [email](mailto://azureapirbcore@microsoft.com) or in our [Teams](https://teams.microsoft.com/l/team/19%3a3ebb18fded0e47938f998e196a52952f%40thread.tacv2/conversations?groupId=1a10b50c-e870-4fe0-8483-bf5542a8d2d8&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) channel. + +Note: The Teams channel is internal MS. \ No newline at end of file From 1341aa2be7ec81e1c52c9aa822ed84ad3f1bbf8a Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Mon, 13 Sep 2021 13:58:45 -0400 Subject: [PATCH 162/729] Update azure/README.md Added ref to style guidelines --- azure/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/README.md b/azure/README.md index 5fcc0b62..14a9db60 100644 --- a/azure/README.md +++ b/azure/README.md @@ -4,7 +4,7 @@ When building software components that will be used by developers to build other Designing powerful APIs with strong defaults, consistent behavior across related projects, and ease of use for developers arises from putting yourself in the shoes of the person using your interfaces, and taking their concerns to heart. The APIs you ship can have a dramatic long term effect on the health of your software product, and that is why the **REST API Stewardship Board** is here to help! We have published a collection of best practices, REST guidance, and OpenAPI style guidelines to help you create an amazing developer experience. * [Considerations for Service Design](ConsiderationsForServiceDesign.md) * [Guidelines](Guidelines.md) - +* [OpenAPI Style Guidelines](https://github.com/Azure/azure-api-style-guide/blob/main/README.md) You can reach out to use via [email](mailto://azureapirbcore@microsoft.com) or in our [Teams](https://teams.microsoft.com/l/team/19%3a3ebb18fded0e47938f998e196a52952f%40thread.tacv2/conversations?groupId=1a10b50c-e870-4fe0-8483-bf5542a8d2d8&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) channel. From 7116b86488aa7a30d0975902965eb8d06358052a Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 24 Sep 2021 08:41:31 -0700 Subject: [PATCH 163/729] Update azure/Guidelines.md --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 29217cd8..883ca829 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -762,7 +762,7 @@ while the Microsoft guidelines use the name "Operation-Location". ### Bring your own Storage (BYOS) Many services need to store and retrieve data files. For this scenario, the service should not implement its own -storage mechanism and should instead leverage the existing Azure Storage service. When doing this, the customer +storage APIs and should instead leverage the existing Azure Storage service. When doing this, the customer "owns" the storage account and just tells your service to use it. Colloquially, we call this Bring Your Own Storage as the customer is bringing their storage account to another service. BYOS provides significant benefits to service implementors: security, performance, uptime, etc. And, of course, most Azure customers are already with the Azure Storage service. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS provides the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders. From 75c5940873c55079068b77d69267ac6a2dc6c022 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 24 Sep 2021 08:42:20 -0700 Subject: [PATCH 164/729] Update azure/Guidelines.md Co-authored-by: tg-msft --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 883ca829..e84240e4 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -763,7 +763,7 @@ while the Microsoft guidelines use the name "Operation-Location". ### Bring your own Storage (BYOS) Many services need to store and retrieve data files. For this scenario, the service should not implement its own storage APIs and should instead leverage the existing Azure Storage service. When doing this, the customer -"owns" the storage account and just tells your service to use it. Colloquially, we call this Bring Your Own Storage as the customer is bringing their storage account to another service. BYOS provides significant benefits to service implementors: security, performance, uptime, etc. And, of course, most Azure customers are already with the Azure Storage service. +"owns" the storage account and just tells your service to use it. Colloquially, we call this Bring Your Own Storage as the customer is bringing their storage account to another service. BYOS provides significant benefits to service implementors: security, performance, uptime, etc. And, of course, most Azure customers are already familiar with the Azure Storage service. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS provides the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders. From bb30fa39bc35bab044d13bb195a29c8cb1e5090e Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 24 Sep 2021 08:45:30 -0700 Subject: [PATCH 165/729] Update azure/Guidelines.md --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index e84240e4..71194b7e 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -769,7 +769,7 @@ While Azure Managed Storage may be easier to get started with, as your service e :white_check_mark: **DO** use the Bring Your Own Storage pattern -:white_check_mark: **DO** use a blob prefix for logical folders +:white_check_mark: **DO** use a blob prefix for a logical folder (avoid terms such as ```directory```, ```folder```, or ```path```). :no_entry: **DO NOT** require a fresh container per operation From edc34b3c9e9507696c9170ed98a5be273cb75600 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 24 Sep 2021 08:56:46 -0700 Subject: [PATCH 166/729] Update azure/Guidelines.md --- azure/Guidelines.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 71194b7e..d3965c59 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -789,7 +789,9 @@ While Azure Managed Storage may be easier to get started with, as your service e :heavy_check_mark: **YOU MAY** allow the customer to provide a [last-modified](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2) timestamp (in RFC1123 format) for read-only files. This allows the client to specify exactly which version of the files your service should use. When reading a file, your service passes this timestamp to Azure Storage using the [if-unmodified-since](https://datatracker.ietf.org/doc/html/rfc7232#section-3.4) request header. If the Storage operation fails with 412, the Storage object was modified and your service operation should return an appropriate 4xx status code and return the Storage error in your operation's 'inner-error' (see guideline above). -:white_check_mark: **DO** allow the customer to specify a URL path to a logical directory (via prefix and delimiter) if your service requires access to multiple files (within this directory). For more information, see [List Blobs API](https://docs.microsoft.com/en-us/rest/api/storageservices/list-blobs) +:white_check_mark: **DO** allow the customer to specify a URL path to a logical folder (via prefix and delimiter) if your service requires access to multiple files (within this folder). For more information, see [List Blobs API](https://docs.microsoft.com/en-us/rest/api/storageservices/list-blobs) + +:heavy_check_mark: **YOU MAY** offer an ```extensions``` field representing an array of strings indicating file extensions of desired blobs within the logical folder. A common pattern when working with multiple files is for your service to receive requests that contain the location(s) of files to process ("input") and a location(s) to place any files that result from processing ("output"). Note: the terms "input" and "output" are just examples; use terms more appropriate to your service's domain. From 80b9e02bcdc7d6f6ebcfef7a056428d508983b1e Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 24 Sep 2021 08:58:01 -0700 Subject: [PATCH 167/729] Update azure/Guidelines.md --- azure/Guidelines.md | 1 + 1 file changed, 1 insertion(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index d3965c59..8a8b1786 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -802,6 +802,7 @@ For example, a service's request body to configure BYOS may look like this: "input":{ "location": "https://mycompany.blob.core.windows.net/documents/english/?", "delimiter": "/", + "extensions" : [ ".bmp", ".jpg", ".tif", ".png" ], "lastModified": "Wed, 21 Oct 2015 07:28:00 GMT" }, "output":{ From 05f4753f38a6a5d9c3f2a8fe15d32c1f5ccf36ad Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Sat, 25 Sep 2021 10:03:58 -0700 Subject: [PATCH 168/729] Update azure/Guidelines.md --- azure/Guidelines.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 8a8b1786..a8ef96a6 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -783,7 +783,9 @@ While Azure Managed Storage may be easier to get started with, as your service e :ballot_box_with_check: **YOU SHOULD** if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text. -:white_check_mark: **DO** return an HTTP status code representing the result of your service operation's behavior. If the operation uses Storage internally, this an implementation detail from the client's perspective of the operation but may happen due to a missing Storage object or insufficient permissions. If the internal Storage operation fails, include the Storage error information in the 'inner-error' section of your operation's response body to help the client know that they may have configured their Storage improperly. +:white_check_mark: **DO** return an HTTP status code representing the result of your service operation's behavior. + +:white_check_mark: **DO** include the Storage error information in the 'inner-error' section of an error response if the error was the result of an internal Storage operation failure. This helps the client determine the underlying cause of the error, e.g.: a missing storage object or insufficient permissions. :white_check_mark: **DO** allow the customer to specify a URL path to a single Storage object if your service requires access to a single file. From e989a57c0b576a9c898c00d8ab84e9944262616c Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 29 Sep 2021 13:34:55 -0400 Subject: [PATCH 169/729] Add files via upload Graph protocol patterns --- graph/ProtocolRuleset.md | 178 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 graph/ProtocolRuleset.md diff --git a/graph/ProtocolRuleset.md b/graph/ProtocolRuleset.md new file mode 100644 index 00000000..0ac48dc7 --- /dev/null +++ b/graph/ProtocolRuleset.md @@ -0,0 +1,178 @@ +[[_TOC_]] + +API owners that have onboarded to Microsoft Graph: + +- [x] Preparing for the API review process +- [x] Designing new APIs or updating existing ones + +and are looking to do one or more of the following: + +- [ ] Understand the requirements of Microsoft Graph APIs +- [ ] Address issues raised by the schema validation CI pipeline +- [ ] Fix existing issues because grace period is expiring + +# Schema validation + +Currently schema validation is run by [Graph-Studio](Update-schema/Graph-Studio) on the build pipeline when workloads push to their test branches or create a pull request to master. +Workloads should have the prerogative to address all the errors raised by the validation and ensure that their schema is as compliant as possible +to the rules defined before publishing their changes. + +There is however the ability to suppress **noncritical errors** for some time, to allow workloads to +plan and address errors that cannot be immediately resolved. Please see [Tracing-and-suppressions](Update-schema/Graph-Studio/Tracing-and-suppressions). + +Error messages can have the following severity levels: + +| Severity | Description | +| :---------- | :----------------------------------------------------------------------------------------------------- | +| Critical | Must be fixed before publishing. Cannot be suppressed. This Error is likely to break AGS if published. | +| Error | Can be suppressed during publishing. This is a hard error that should be fixed before moving to v1.0. | +| Warning | Can be suppressed during publishing. This is a suggestion so as to conform to our coding style. | +| Information | No need to suppress. It will not block publishing. This is for information purposes only. | + +# OData validation + +Graph Studio performs the full suite of OData validations. Because Microsoft Graph rejects any schema containing OData violations, these violations are treated as `Critical` errors and cannot be suppressed. OData violation error codes are prefixed with `Schema.OData.{EdmErrorCode}`. + +Example OData infraction: + +```log +2020-11-30 21:47:51Z Critical Schema.OData.InvalidName: /Schemas/beta-Prod.csdl: [env=Prod;version=beta] '/ComplexType[testType]/Property[invalidProperty ]' The specified name is not allowed: 'invalidProperty '. +``` + +For a complete list of the OData error codes please see the [OData Validation Documentation](https://docs.microsoft.com/en-us/dotnet/api/microsoft.odata.edm.validation.edmerrorcode). + +# Breaking change analysis + +Breaking change analysis is performed by comparing latest (master) schema with current local schema. Each entry provides the file where the breaking change has occurred, and the element which triggered the error. The errors fall into one of three buckets: + +- `Schema.BreakingChange.CannotAdd` +- `Schema.BreakingChange.CannotChange` +- `Schema.BreakingChange.CannotDelete` + +Making changes to AGS annotations is not considered breaking, except adding `ags:IsHidden="true"` which makes an already public API private. Making changes to existing elements, or removing existing elements is considered breaking. Adding new elements is allowed and not considered breaking, but there are exceptions: + +- Adding `EnumType` members for non-extensible enumerations is considered breaking. +- Adding `Nullable="false"` properties to existing types is considered breaking. +- Adding `Nullable="false"` parameters to existing actions and functions is considered breaking. +- Adding attributes to existing nodes is considered breaking. + - Adding AGS annotations is exempted, except `ags:IsHidden="true"`. + - Adding `OpenType="true"` is exempted. + +# Validation for Private Preview API changes + +Private preview API validation is performed by comparing latest (master) schema with current local schema. Each entry provides the file where the validation has occurred, and the element which triggered the error. The errors fall into one of the following buckets: + +- `PrivatePreview.IsNotHidden` +- `PrivatePreview.Deprecated` +- `PrivatePreview.DeprecationDate` +- `PrivatePreview.RemovalDate` + +The following are the rules that private preview API changes must follow: + +- All elements added must be marked as hidden. +- All elements added must be deprecated. +- Deprecation date must be earlier than the current date. +- Removal date must not be later than 90 days from deprecation date. + +# JSON Description Validation for Public Schema Changes + +Pull requests with public schema changes need to have the below duly filled Json template as part of the pull request description: Please see [Pull-request-json-description](Update-schema/Pull-request-json-description). + +If `IsAPIForPrivatePreview` is set to "Yes", then all changes need to have `ags:IsHidden="true"` and not appear in the final public metadata. +In this case, `ChangelogPullRequestUrl` and `DocumentationPullRequestUrl` can be left blank. +No change will go in without having `IsPrivacyReviewCompleted` set as yes and a valid `PrivacyReviewUrl`. Please see [Privacy-review](Privacy-review). + +Errors from validation of the pull request description fall into one of the following buckets: + +| Code | Severity | Description | +| :---------------------------------------------------- | :--------- | :------------------------------------------------------------------------------------ | +| `Schema.PullRequest.InvalidDescriptionJson` | `Critical` | The pull request description Json is missing or has some formatting issues. | +| `Schema.PullRequest.ValueMissingError` | `Critical` | A required value is missing from the description Json. | +| `Schema.PullRequest.KeyMissingError` | `Critical` | A required key is missing from the description Json. | +| `Schema.PullRequest.PrivacyReviewNotCompleted` | `Critical` | The IsPrivacyReviewCompleted value must be set to True / Yes. | +| `Schema.PullRequest.InvalidPrivacyReviewUrl` | `Critical` | The PrivacyReviewUrl is not a valid privacy review URL. | +| `Schema.PullRequest.InvalidGithubUrl` | `Critical` | The DocumentationPullRequestUrl / ChangelogPullRequestUrl must be a valid Github URL. | +| `Schema.PullRequest.InvalidAPIReviewUrl` | `Critical` | The APIReviewApprovalPullRequestUrl is not a valid API review URL. | +| `Schema.PullRequest.PullRequestFetchError` | `Critical` | There was an error fetching the API review pull request. | +| `Schema.PullRequest.APIReviewPullRequestNotCompleted` | `Critical` | The API review pull request is not complete. | + +# Microsoft Graph ruleset + +Beyond OData and Breaking Change analysis, Graph Studio performs its own set of schema validations. These are typically best practices and naming conventions, but could also find semantic issues with the schemas. Before a schema can be published we must validate that it can be loaded by Microsoft Graph, which has its own set of criteria as to what makes a schema valid across all workloads. Because Microsoft Graph will reject any schemas not conforming to its criteria, any infractions are handled as `Critical` errors and cannot be suppressed. + +| Code | Severity | Description | +| :------------------------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Naming Validation** | | | +| `Schema.Validation.CamelCase` | `Error` | Names must be in lower camel case. | +| `Schema.Validation.NamespaceCamelCase` | `Error` | Namespaces must be in lower camel case. | +| `Schema.Validation.UseEmail` | `Warning` | A property name should use `email` instead of `mail`. | +| `Schema.Validation.SuffixTime` | `Error` | If a property has the type `Edm.Time`, its name must end in `Time` e.g. `startTime`. | +| `Schema.Validation.SuffixDate` | `Error` | If a property has the type `Edm.Date`, its name must end in `Date` (e.g. `birthDate`) or `MonthYear` (e.g. `startMonthYear`). | +| `Schema.Validation.SuffixDateTime` | `Error` | If a property has the type `DateTimeOffset`, its name must end in `DateTime` (e.g. `receivedDateTime`). | +| *`Schema.Validation.Case2LetterAcronyms` | `Error` | 2 letter acronyms should be cased with the same case (e.g. `prDescription`, `availableOnPC`). | +| *`Schema.Validation.Case3PlusLetterAcronyms` | `Error` | 3+ letter acronyms should be cased the same way as regular words (e.g. `adoPipeline`, `advancedRpc`). | +| **Primary key validation** | | | +| `Schema.Validation.EntityKeyMustBeString` | `Error` | Check to verify that the primary id of an entity type is `string`. | +| `Schema.Validation.PrimaryKeyMustBeDefinedAsProperty` | `Error` | Check to verify that the primary key must also be defined as a property. | +| `Schema.Validation.PrimaryKeyMustNotBeComposite` | `Error` | Check to verify that the primary key is composed of a single property and not multiple. | +| `Schema.Validation.AvoidComplexTypeId` | `Error` | A complex type must not have the property `id`. | +| **Property name validation** | | | +| `Schema.Validation.PropertyMustNotBeNamedType` | `Error` | A property name should not be `"type"`. | +| `Schema.Validation.PropertyNamesShouldNotStartWithTypeName` | `Error` | Property names should not start with type name. | +| `Schema.Validation.PropertyNameMustNotEndInPrimitiveType` | `Error` | Property names must not end in primitive types unless the type is temporal. | +| `Schema.Validation.SingularNoun` | `Warning` | Non-collection property names should be singular. | +| `Schema.Validation.PluralNoun` | `Warning` | Collection property names should be plural. | +| `Schema.Validation.EntityTypeNameShouldBeSingular` | `Warning` | Entity type name should be singular. | +| **Enum validation** | | | +| `Schema.Validation.EnumShouldBeEvolvable` | `Warning` | Enums should be evolvable. | +| `Schema.Validation.EnumMemberValuesShouldBeZeroOrPowersOfTwo` | `Warning` | Consider using zero or powers of two for flag enum member values. | +| **Stream validation** | | | +| `Schema.Validation.MediaEntityTypesCannotContainSubstreams` | `Warning` | Streams must not define a property of type `Edm.Stream`. | +| `Schema.Validation.MediaEntityTypesCannotInheritFromABaseType` | `Warning` | Streams cannot inherit from a base type. | +| **Structure validation** | | | +| `Schema.Validation.OperationsMustBeBound` | `Error` | Actions and Functions must have an IsBound='true' attribute and the first parameter must be the binding parameter. This is an AGS limitation. | +| `Schema.Validation.NavigationPropertyBindingMissing` | `Warning` | `NavigationProperty` without `ContainsTarget` must define `NavigationPropertyBinding` in Singleton/EntitySet. | +| `Schema.Validation.OperationsShouldBeAvoided` | `Warning` | Operations with names containing add, create, update, delete or remove should be avoided whenever possible. | +| *`Schema.Validation.ParallelCollections` | `Error` | Do not use parallel collections; use collections of complex types instead. | +| *`Schema.Validation.ProperCollections` | `Warning` | Consider using a proper collection rather than `property1`, `property2`, etc. | +| *`Schema.Validation.EntitySetNavigationProperties` | `Error` | Entity sets should have valid navigation properties. | +| *`Schema.Validation.SingletonNavigationProperties` | `Error` | Singletons should have valid navigation properties. | +| **Cross schema validation** | | | +| `Schema.Validation.EntityWithoutMaster` | `Critical` | Ensure that all entities have the ags:IsMaster="true" or ags:IsShared="true" annotation. | +| `Schema.Validation.TypeOverridesBaseProperty` | `Critical` | Types cannot override their base properties. | +| `Schema.Validation.PropertyAlreadyExists` | `Critical` | Entity type cannot redefine properties already defined by another workload. | +| `Schema.Validation.InconsistentSharedType` | `Critical` | Ensure that shared type definitions are consistent across workloads.
If multiple workloads define a shared type, they must have the exact same definition of that type. | +| `Schema.Validation.ElementAlreadyExists` | `Critical` | Different elements cannot share the same name.
If you have an entity called `foo`, you cannot have an action `foo` in the same namespace. | +| `Schema.Validation.NavigationPropertyContainsForeignTarget` | `Error` | Navigation property cannot contain target from a different workload. | + +\*Validation has not yet been automated in Graph Studio. However, workloads should adhere to these specifications so as not to risk having their schemas broken in the future. + +# Microsoft Graph protocol ruleset (Not implemented yet) + +| Name | Severity | +| ----------------------------------------------------------------------------------------------- | -------- | +| **Request patterns** | | +| ✔ DO use `GET …/{collection}` and `GET …/{collection}/{id}` for listing and reading resources. | Error | +| ✔ DO use `POST …/{collection}` for creating resources. | Error | +| ✔ DO use `PATCH …/{collection}/{id}` for updating resources. | Error | +| ✖ AVOID using `PUT …/{collection}/{id}` for updating resources. | Warning | +| ✖ DO NOT use `PATCH` to replaces resources or `PUT` to partially update resources. | Error | +| ✖ AVOID patterns that require multiple round trips to complete a single logical action. | Warning | +| ✔ CONSIDER supporting `return`, and `omit-nulls` preferences. | Warning | +| **Serialization** | | +| ✔ DO use an object as the root of all JSON payloads. | Error | +| ✔ DO use a `value` property in the root object to return a collection. | Error | +| ✔ DO include `@odata.type` annotations when the type is ambiguous. | Warning | +| **Authorization** | | +| ✖ DO NOT use a scope ending with `.Read` to authorize a data modification. | Error | +| **Errors** | | +| ✔ DO return an `error` property with a child `code` property in all error responses. | Error | +| ✔ DO return a `403 Forbidden` error when insufficient scopes are present on the auth token. | Error | +| ✔ CONSIDER returning a `404 Not found` error if a `403` would result in information disclosure. | Warning | +| ✔ DO return a `429 Too many requests` error when the caller has exceeded throttling limits. | Error | + +# Contacts + +| Area | Contact | +| :------ | :------------------------------------------------------------------------------------------------ | +| Support | [StackOverflow](https://stackoverflow.microsoft.com/questions/tagged/1096) tag `[MicrosoftGraph]` | From 1b6432a124dc868b2e19de0191bd6688b7dcca95 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 3 Oct 2021 12:27:21 -0500 Subject: [PATCH 170/729] Reference RFC 1123 for header date format --- azure/Guidelines.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ea4571d0..7507e277 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -159,7 +159,7 @@ Integer | -253+1 to +253-1 (for consistency with JSON li Float | [IEEE-754 binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) String | (Un)quoted?, max length, legal characters, case-sensitive, multiple delimiter UUID | 123e4567-e89b-12d3-a456-426614174000 (no {}s, hyphens, case-insensitive) [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) -Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) +Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT [RFC 1123, Section 5.2.14](https://datatracker.ietf.org/doc/html/rfc1123#page-55) Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ (with at most 3 digits of fractional seconds) [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) Byte array | Base-64 encoded, max length Array | One of a) a comma-separated list of values (preferred), or b) separate `name=value` parameter instances for each value of the array @@ -195,7 +195,9 @@ retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://da :white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it -:white_check_mark: **DO** accept and return date values in headers using the HTTP Date format as defined in [RFC 7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT" +:white_check_mark: **DO** accept and return date values in headers using the HTTP Date format as defined in [RFC 1123, Section 5.2.14](https://datatracker.ietf.org/doc/html/rfc1123#page-55), e.g. "Sun, 06 Nov 1994 08:49:37 GMT". + +Note: RFC 1123 defines the date format as a modification of the date format in [RFC 822, Section 5](https://datatracker.ietf.org/doc/html/rfc822#section-5) to support either a 2 or 4 digit year, and further recommends that a 4 digit year always be used. :no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated From 2788fd509c1d04c322a21e7a3c8d13cf24846a4f Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 5 Oct 2021 18:43:18 -0400 Subject: [PATCH 171/729] Template This is the template to describe common Microsoft Graph API patterns for reuse during design and implementation. --- graph/PatternDescriptionTemplate.md | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 graph/PatternDescriptionTemplate.md diff --git a/graph/PatternDescriptionTemplate.md b/graph/PatternDescriptionTemplate.md new file mode 100644 index 00000000..1d47ba65 --- /dev/null +++ b/graph/PatternDescriptionTemplate.md @@ -0,0 +1,53 @@ +Pattern Name {align="center" style="text-align:center"} +============ + +Microsoft Graph API Design Pattern + +  + +*Provide a short description of the pattern.* + +Context +------- + +*Describe business context relevant for the pattern.* + +* * + +Problem: + +*Provide a short description of the problem.* + +* * + +Solution +-------- + +*Describe how to implement the solution to solve the problem* + +* * + +Issues and Considerations +------------------------- + +*Describe tradeoffs of the solution.* + +  + +When to Use this Pattern +------------------------ + +*Describe when and why the solution is applicable and when it may not.* + +  + +Example +------- + +*Provide a short example from real life* + +  + +  + +  From 1564dad5f25405a8b24550f13967d24f2303b2ee Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 6 Oct 2021 10:34:41 -0400 Subject: [PATCH 172/729] Update PatternDescriptionTemplate.md Added reference to related patterns --- graph/PatternDescriptionTemplate.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/PatternDescriptionTemplate.md b/graph/PatternDescriptionTemplate.md index 1d47ba65..5804ed3a 100644 --- a/graph/PatternDescriptionTemplate.md +++ b/graph/PatternDescriptionTemplate.md @@ -23,8 +23,8 @@ Problem: Solution -------- -*Describe how to implement the solution to solve the problem* - +*Describe how to implement the solution to solve the problem.* +*Describe related patterns.* * * Issues and Considerations From 38df7451d46e5cede1b44d96da12c9ef95935b43 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 8 Oct 2021 07:21:47 -0500 Subject: [PATCH 173/729] Update guidance on path structure for actions --- azure/ConsiderationsForServiceDesign.md | 16 ++++++++++++++++ azure/Guidelines.md | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 600805fa..e0a6f6e2 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -119,6 +119,22 @@ As you build out your service and API, there are a number of decisions that can :ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. +## Action Operations + +Most operations conform to one of the standard REST Create, Read, Update, Delete, or List (CRUDL) style of operations. We refer to all other operations as "action" operations. Some examples of action operations are to reboot a VM, or send an email. + +It is good practice to define the path for action operations that is easily distinguished from any resource path of the service. When services allow user-specified resource ids (also a good practice), the recommended approach for this is: +1) constrain user-specified resource ids to allow only certain characters, such as alphanumeric and '-' or '_', and +2) use a special character not in the set of valid characters for resource names to distinguish the "action" in the path. + +In Azure we recommend using ':' as the character to distinguish action paths. E.g. +```http +https://...//:? +``` + +Other patterns are possible. The key consideration is to ensure that the path for an action operation +cannot collide with a resource path that contains user-specified resource ids. + ## Long-Running Operations Long-running operations are an API design pattern that should be used when the processing of diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 7507e277..2d552e1a 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -427,7 +427,7 @@ Both Rectangle and Circle have common fields: `kind`, `fillColor`, `lineColor`, ### Performing an Action The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image, sending an SMS message. It is also sometimes useful to perform an action on a collection. -:white_check_mark: **DO** pattern your URL like this to perform an action on a resource +:ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a resource **URL Pattern** ```http https://...//:? @@ -441,7 +441,7 @@ https://.../users/Bob:send-sms?text="Hello" **Equivalent to (in C#)** ```users["Bob"].SendSms("Hello")``` -:white_check_mark: **DO** pattern your URL like this to perform an action on a collection +:ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a collection **URL Pattern** ```http https://.../:? From 0f1a2ea21805ef9f5305991a64222b246612e376 Mon Sep 17 00:00:00 2001 From: Ralf Beckers Date: Thu, 21 Oct 2021 08:44:07 +0200 Subject: [PATCH 174/729] Fixed separator in link to relative local file Backslash produced incorrect link in doc. Fixed with forward slash. --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 7507e277..ae2e2e3d 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -995,4 +995,4 @@ These guidelines describe the upfront design considerations, technology building The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. -These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. For more information on how to part with the Stewardship board, please refer to [Considerations for Service Design](.\ConsiderationsForServiceDesign.md). +These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. For more information on how to part with the Stewardship board, please refer to [Considerations for Service Design](./ConsiderationsForServiceDesign.md). From 97a9251b15bebcbfedbbb07ac401c8675b4900db Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 22 Oct 2021 13:11:41 -0400 Subject: [PATCH 175/729] test commit --- graph/ProtocolRuleset.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/ProtocolRuleset.md b/graph/ProtocolRuleset.md index 0ac48dc7..72a8c3c2 100644 --- a/graph/ProtocolRuleset.md +++ b/graph/ProtocolRuleset.md @@ -7,7 +7,7 @@ API owners that have onboarded to Microsoft Graph: and are looking to do one or more of the following: -- [ ] Understand the requirements of Microsoft Graph APIs +- [x] Understand the requirements of Microsoft Graph APIs - [ ] Address issues raised by the schema validation CI pipeline - [ ] Fix existing issues because grace period is expiring From 5713410b84a7e2b052da008b8dff21d3417ee9c8 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 22 Oct 2021 14:24:44 -0400 Subject: [PATCH 176/729] Create Modelling with Subtypes Pattern.md --- graph/Modelling with Subtypes Pattern.md | 149 +++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 graph/Modelling with Subtypes Pattern.md diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md new file mode 100644 index 00000000..dc4e0735 --- /dev/null +++ b/graph/Modelling with Subtypes Pattern.md @@ -0,0 +1,149 @@ +Type Hierarchy {align="center" style="text-align:center"} +============== + +Microsoft Graph API Design Pattern + +  + +*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +* * +--- + +Context +------- + +Let’s assume you need to model an API to manage groups in an +organization, where employees can create groups and become owners of the +group by default. At the same time to support business processes some +groups may be created automatically by daemon applications using a +service principal account. In this case the service principle will +become the group owner. People and service principles have some common +and some unique properties such as both have unique identifiers and +credentials, but users will have additional properties such as email and +manager for example. Conversely a service principle won’t have a manager +assigned but may have an associated application identifier and a +description. + +Additional requirement for the API model is to be flexible and +accommodate future needs like a device may become the owner of a group. + +Problem + +How to model group owners to allow a heterogeneous collection of users +and principles so that API design can reflect common and relevant +properties for each subset of owners and accommodate new owner types in +future? + +Solution +-------- + +OData allows us to design collections of entities (entity sets, multi +valued navigation properties) with values of different types using +**type hierarchy**, where there is one abstract base type with a few +common properties and one sub-type for each variant of the entity. In +the current version of Microsoft Graph there are many collections of +items that represent slightly different things, variants of one concept. + +Issues and Considerations +------------------------- + +When introducing a new subtype, you need to ensure that the new subtype +doesn't change the semantic of the type hierarchy with it's implicit +constraints. + +There are a **few potential risks** for client applications when new +sub-types are introduced: + +·         De-serialization code might break because of missing +properties in returned collection items. Even though property X was +mandatory on all subtypes previously returned, the new subtype might not +have this property and the client code needs to deal with that. + +·         Client libraries for strongly typed language might ignore some +of the values in the @odata.type property without further configuration +and need to be configured to be able to pick the right (client) type to +deserialize into. + +To minimize impact on clients type hierarchy can be refined by +annotating the collections with OData derived type constraints (see +validation vocabulary). This annotation restricts the values to certain +sub-trees of an inheritance hierarchy. It makes it very explicit that +the collection only contains elements of some of the subtypes and helps +to not return objects of a type that is semantically not suitable. In +addition, you can follow some of the mitigation techniques such as: + +·         Avoid overgeneralized base types + +·         Think about roll-out sequence + +Consider that Microsoft Graph does not return objects from a workload +that has a type that is not configured in current metadata. To avoid +inconsistencies, follow a two-step process: + +−        Introduce the entity type to the Graph metadata but don’t +return objects of the type in any of the heterogeneous collections. + +−        Enable your workload to return objects of the new type as items +of collection. + +·         Allow time for testing + +o   Inform the clients about the change and allow them to test the +changes in beta. Time is required to implement the code necessary to +deal with the new entity type, both in terms of de-serialization as well +as integrating it into the rest of the application. + +·         Communicate the change in semantics + +It is necessary for the client developers to incorporate the new +semantic into their application/service, even if the change is perceived +to be small. This requires early communication and clear documentation +of what the new type represents and why/how it is considered a subtype +of the original abstract type of the collection. + +When to Use this Pattern +------------------------ + +The Type hierarchy pattern is well familiar to OOP developers and well +suited for strongly typed client programming languages. + +There are related patterns to consider such as +[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) +and [Flat bag of +properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). + +Example +------- + +GET +[https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners](https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners)  +returns a collection where each element can be a user or a service +principal, and has an additional property @odata.type to show subtype +for each variant: + +{ + +    "@odata.context": +"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", + +    "value": [ + +        { + +            "@**odata.type**": "\#**microsoft.graph.user**", + +            "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", + +            "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" + +            // ... + +        } + +    ] + +} + +  From 09779bbdebf597485061da6d73f1912949a9d505 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 22 Oct 2021 15:31:08 -0400 Subject: [PATCH 177/729] Fixed formatting --- graph/Modelling with Subtypes Pattern.md | 48 +++++++++++------------- graph/PatternDescriptionTemplate.md | 18 ++++----- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index dc4e0735..5b05f4b8 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -1,5 +1,4 @@ -Type Hierarchy {align="center" style="text-align:center"} -============== +# Type Hierarchy Microsoft Graph API Design Pattern @@ -8,11 +7,8 @@ Microsoft Graph API Design Pattern *A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -* * ---- -Context -------- +## Context Let’s assume you need to model an API to manage groups in an organization, where employees can create groups and become owners of the @@ -29,14 +25,14 @@ description. Additional requirement for the API model is to be flexible and accommodate future needs like a device may become the owner of a group. -Problem +## Problem How to model group owners to allow a heterogeneous collection of users and principles so that API design can reflect common and relevant properties for each subset of owners and accommodate new owner types in future? -Solution +## Solution -------- OData allows us to design collections of entities (entity sets, multi @@ -46,7 +42,7 @@ common properties and one sub-type for each variant of the entity. In the current version of Microsoft Graph there are many collections of items that represent slightly different things, variants of one concept. -Issues and Considerations +## Issues and Considerations ------------------------- When introducing a new subtype, you need to ensure that the new subtype @@ -56,12 +52,12 @@ constraints. There are a **few potential risks** for client applications when new sub-types are introduced: -·         De-serialization code might break because of missing +- De-serialization code might break because of missing properties in returned collection items. Even though property X was mandatory on all subtypes previously returned, the new subtype might not have this property and the client code needs to deal with that. -·         Client libraries for strongly typed language might ignore some +- Client libraries for strongly typed language might ignore some of the values in the @odata.type property without further configuration and need to be configured to be able to pick the right (client) type to deserialize into. @@ -74,36 +70,36 @@ the collection only contains elements of some of the subtypes and helps to not return objects of a type that is semantically not suitable. In addition, you can follow some of the mitigation techniques such as: -·         Avoid overgeneralized base types +- Avoid overgeneralized base types -·         Think about roll-out sequence +- Think about roll-out sequence -Consider that Microsoft Graph does not return objects from a workload -that has a type that is not configured in current metadata. To avoid -inconsistencies, follow a two-step process: +>>>Consider that Microsoft Graph does not return objects from a workload +>>>that has a type that is not configured in current metadata. To avoid +>>>inconsistencies, follow a two-step process: -−        Introduce the entity type to the Graph metadata but don’t +>>>Introduce the entity type to the Graph metadata but don’t return objects of the type in any of the heterogeneous collections. -−        Enable your workload to return objects of the new type as items +>>> Enable your workload to return objects of the new type as items of collection. -·         Allow time for testing +- Allow time for testing -o   Inform the clients about the change and allow them to test the +>>>Inform the clients about the change and allow them to test the changes in beta. Time is required to implement the code necessary to deal with the new entity type, both in terms of de-serialization as well as integrating it into the rest of the application. -·         Communicate the change in semantics +- Communicate the change in semantics -It is necessary for the client developers to incorporate the new +>>>It is necessary for the client developers to incorporate the new semantic into their application/service, even if the change is perceived to be small. This requires early communication and clear documentation of what the new type represents and why/how it is considered a subtype of the original abstract type of the collection. -When to Use this Pattern +## When to Use this Pattern ------------------------ The Type hierarchy pattern is well familiar to OOP developers and well @@ -114,7 +110,7 @@ There are related patterns to consider such as and [Flat bag of properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). -Example +## Example ------- GET @@ -122,7 +118,7 @@ GET returns a collection where each element can be a user or a service principal, and has an additional property @odata.type to show subtype for each variant: - +```js {     "@odata.context": @@ -145,5 +141,5 @@ for each variant:     ] } - +```   diff --git a/graph/PatternDescriptionTemplate.md b/graph/PatternDescriptionTemplate.md index 5804ed3a..89886cca 100644 --- a/graph/PatternDescriptionTemplate.md +++ b/graph/PatternDescriptionTemplate.md @@ -1,5 +1,4 @@ -Pattern Name {align="center" style="text-align:center"} -============ +# Pattern Name Microsoft Graph API Design Pattern @@ -7,41 +6,40 @@ Microsoft Graph API Design Pattern *Provide a short description of the pattern.* -Context -------- +## Context *Describe business context relevant for the pattern.* * * -Problem: +## Problem +-------- *Provide a short description of the problem.* * * -Solution +## Solution -------- *Describe how to implement the solution to solve the problem.* *Describe related patterns.* * * -Issues and Considerations +## Issues and Considerations ------------------------- *Describe tradeoffs of the solution.* -   -When to Use this Pattern +## When to Use this Pattern ------------------------ *Describe when and why the solution is applicable and when it may not.*   -Example +## Example ------- *Provide a short example from real life* From 771744efcef253ac3611fbe342b495e79f8fbfa5 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 22 Oct 2021 16:12:33 -0400 Subject: [PATCH 178/729] Check boxes --- graph/Modelling with Subtypes Pattern.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index 5b05f4b8..88f4ff4f 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -142,4 +142,15 @@ for each variant: } ``` -  +### Prescriptive Guidance +This document offers prescriptive guidance labeled as follows: + +:white_check_mark: **DO** fulfill this specification. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. + +:ballot_box_with_check: **YOU SHOULD** fulfill this specification. If not following this advice, you MUST disclose your reason during the Graph API review. + +:heavy_check_mark: **YOU MAY** satisfy this specification if appropriate to your situation. No notification to the Graph API Review board is required. + +:warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Graph API Review board review. + +:no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Graph API Review board **prior** to implementation.  From 64b3c93143a16088bb2c16803bab2f25b9fad25d Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sat, 23 Oct 2021 11:49:16 -0700 Subject: [PATCH 179/729] Address PR review comments --- azure/ConsiderationsForServiceDesign.md | 2 +- azure/Guidelines.md | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index e0a6f6e2..be1562c6 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -127,7 +127,7 @@ It is good practice to define the path for action operations that is easily dist 1) constrain user-specified resource ids to allow only certain characters, such as alphanumeric and '-' or '_', and 2) use a special character not in the set of valid characters for resource names to distinguish the "action" in the path. -In Azure we recommend using ':' as the character to distinguish action paths. E.g. +In Azure we recommend distinguishing action operations by appending a ':' followed by an action verb to the final path segment. E.g. ```http https://...//:? ``` diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 2d552e1a..c3d80737 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -203,7 +203,7 @@ Note: RFC 1123 defines the date format as a modification of the date format in [ :no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. -#### Additional References +**Additional References** - [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) - [Standard HTTP Headers](https://httpwg.org/specs/rfc7231.html#header.field.registration) - [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) @@ -425,7 +425,7 @@ Both Rectangle and Circle have common fields: `kind`, `fillColor`, `lineColor`, ## Common API Patterns ### Performing an Action -The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image, sending an SMS message. It is also sometimes useful to perform an action on a collection. +The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image or rebooting a VM. It is also sometimes useful to perform an action on a collection. :ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a resource **URL Pattern** @@ -435,12 +435,9 @@ https://...//:? **Example** ```http -https://.../users/Bob:send-sms?text="Hello" +https://.../users/Bob:grant?access=read ``` -**Equivalent to (in C#)** -```users["Bob"].SendSms("Hello")``` - :ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a collection **URL Pattern** ```http @@ -460,8 +457,9 @@ Note: To avoid potential collision of actions and resource ids, you should disal :white_check_mark: **DO** return a `200-OK` when the action completes synchronously and successfully. -:ballot_box_with_check: **YOU SHOULD** use a verb to name your action. +:ballot_box_with_check: **YOU SHOULD** use a verb as the `` component of the path. +:no_entry: **DO NOT** use an action operation when the operation behavior could reasonably be defined as one of the standard REST Create, Read, Update, Delete, or List operations. ### Collections :white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources. @@ -984,7 +982,8 @@ Client libraries are required to send telemetry and distributed tracing informat :white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. :no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. -#### Additional References + +**Additional References** - [Azure SDK client guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) - [Azure SDK User-Agent header policy](https://azure.github.io/azure-sdk/general_azurecore.html#azurecore-http-telemetry-x-ms-useragent) - [Azure SDK Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) From 4de14b7e21dd70e39ba076406886d34442793e11 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 31 Oct 2021 08:15:45 -0700 Subject: [PATCH 180/729] A passel of minor fixes. --- azure/Guidelines.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index f4dfdefc..d3b751dc 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -64,7 +64,7 @@ Where: | - | - | | service | Name of the service (ex: blobstore, servicebus, directory, or management) | cloud | Cloud domain name, e.g. `azure.net` (see Azure CLI's "az cloud list") - | tenant | Globally-unique ID of container representing tenant isolation, billing, enforced quotas, lifetime of containees (ex: subscription UUID) + | tenant | Globally-unique ID of container representing tenant isolation, billing, enforced quotas, lifetime of containers (ex: subscription UUID) | service‑root | Service-specific path (ex: blobcontainer, myqueue) | resource‑collection | Name of the collection, unabbreviated, pluralized | resource‑id | Value of the unique id property. This MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. @@ -81,7 +81,7 @@ Some customer-provided path segment values may be compared case-insensitivity if :white_check_mark: **DO** restrict the characters in service-defined path segments to `0-9 A-Z a-z - . _ ~`, with `:` allowed only as described below to designate an action operation. -:ballot_box_with_check: **YOU SHOULD** restrict the characters allowed in user-specified path segements (i.e. path parameters values) to `0-9 A-Z a-z - . _ ~` (do not allow `:`). +:ballot_box_with_check: **YOU SHOULD** restrict the characters allowed in user-specified path segments (i.e. path parameters values) to `0-9 A-Z a-z - . _ ~` (do not allow `:`). :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) @@ -170,9 +170,9 @@ The table below lists the headers most used by Azure services: Header Key | Applies to | Example ------------------- | ---------- | ------------- _authorization_ | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) -_x-ms-useragent_ | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry)) -traceparent | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry)) -tracecontext | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing-&-Telemetry)) +_x-ms-useragent_ | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) +traceparent | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) +tracecontext | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) accept | Request | application/json If-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) If-None-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) @@ -181,7 +181,7 @@ If-Unmodified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Condition date | Both | Sun, 06 Nov 1994 08:49:37 GMT (see [RFC7231, Section 7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2)) _content-type_ | Both | application/merge-patch+json _content-length_ | Both | 1024 -_x-ms-request-id_ | Response | [see Customer Support](http://TODO:link-goes-here) +_x-ms-request-id_ | Response | 4227cdc5-9f48-4e84-921a-10967cb785a0 ETag | Response | "67ab43" (see [Conditional Requests](#Conditional-Requests)) last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT _x-ms-error-code_ | Response | (see [Handling Errors](#Handling-Errors)) @@ -199,6 +199,10 @@ retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://da Note: RFC 1123 defines the date format as a modification of the date format in [RFC 822, Section 5](https://datatracker.ietf.org/doc/html/rfc822#section-5) to support either a 2 or 4 digit year, and further recommends that a 4 digit year always be used. +:white_check_mark: **DO** create an opaque value that uniquely identifies the request and return this value in the `x-ms-request-id` response header. + +Your service should include the `x-ms-request-id` value in error logs so that users can submit support requests for specific failures using this value. + :no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated :no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. @@ -674,7 +678,7 @@ Azure services need to change over time. However, when changing a service, there :white_check_mark: **DO** review any API changes with the Azure API Stewardship Board -:white_check_mark: **DO** use an `api-version` query parameter with a date value +:white_check_mark: **DO** use an `api-version` query parameter with a `YYYY-MM-DD` date value, with a `-preview` suffix for a preview service. ```http PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 @@ -806,7 +810,7 @@ Some common situations where the RELO pattern should be used: :white_check_mark: **DO** support a get method on the resource that returns a representation of the resource including the status field that indicates when the operation has completed. -:white_check_mark: **DO** define the "status" field of the resource as an enum with all the values it may contain including the "terminal" values "Succeeded", "Failed", and "Canceled". +:white_check_mark: **DO** define the "status" field of the resource as an enum with all the values it may contain including the "terminal" values "Succeeded", "Failed", and "Canceled". See [Enums & SDKs](#enums--sdks-client-libraries). :ballot_box_with_check: **YOU SHOULD** use the name `status` for the "status" field of the resource. @@ -963,8 +967,8 @@ When supporting optimistic concurrency: | PATCH / PUT | `If-None-Match` | * | check for _any_ version of the resource, if one is found, fail the operation | `412-Precondition Failed` | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| | PATCH / PUT | `If-Match` | value of ETag | value of `If-Match` equals the latest ETag value on the server, confirming that the version of the resource is the most current | `200-OK` or
`201-Created`
| Response header MUST include the new `ETag` value. Response body SHOULD include the serialized value of the resource (typically JSON). | | PATCH / PUT | `If-Match` | value of ETag | value of `If-Match` header DOES NOT equal the latest ETag value on the server, indicating a change has ocurred since after the client fetched the resource| `412-Precondition Failed` | Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.| -| DELETE | `If-None-Match` | value of ETag | value does NOT match the latest value on the server | `412-Preconditioned Failed` | Response body SHOULD be empty.| -| DELETE | `If-None-Match` | value of ETag | value matches the latest value on the server | `204-No Content` | Response body SHOULD be empty. | +| DELETE | `If-Match` | value of ETag | value matches the latest value on the server | `204-No Content` | Response body SHOULD be empty. | +| DELETE | `If-Match` | value of ETag | value does NOT match the latest value on the server | `412-Preconditioned Failed` | Response body SHOULD be empty.| #### Computing ETags The strategy that you use to compute the `ETag` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the `ETag`. Another common strategy for determining the value of an `ETag` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. From 34f647fb55717d09bc28f8f42470d19e282daf4f Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 5 Nov 2021 16:44:19 -0400 Subject: [PATCH 181/729] Need formatting --- graph/GuidelinesGraph.md | 599 +++++++++++++++++++++++ graph/Modelling with Subtypes Pattern.md | 19 +- 2 files changed, 607 insertions(+), 11 deletions(-) create mode 100644 graph/GuidelinesGraph.md diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md new file mode 100644 index 00000000..344c8e8e --- /dev/null +++ b/graph/GuidelinesGraph.md @@ -0,0 +1,599 @@ +# Microsoft Graph REST API Guidelines + +Table of Contents + +[Microsoft Graph REST API Guidelines](#_Toc86861191) + +[Introduction](#_Toc86861192) + +[Design Approach](#design-approach) + +[Naming](#_Toc86861194) + +[Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) + +[Recommended Modeling Patterns](#_Toc86861196) + +[Behavior Modeling](#behavior-modeling) + +[Error Handling](#error-handling) + +[API contract and non-backward compatible +changes](#api-contract-and-non-backward-compatible-changes) + +[Versioning and Deprecation](#versioning-and-deprecation) + +[Common API Patterns](#common-api-patterns) + +[Final thoughts](#final-thoughts) + +## + +#### History + +| Date | Notes | +|-------------|-----------------------------| +| 2021-Sep-28 | Alignment with Azure style. | +| 2020-Oct-04 | Initial version in Wiki. | + +## Introduction + +When building a digital ecosystem providing APIs that are easy to discover, +simple to use, fit to purpose, and consistent across your products can make the +difference between success and failure. + +This document offers guidance that Graph API developer teams MUST follow to +ensure that customers have a great experience. A new API design should meet the +following goals: + +\- Developer friendly via consistent naming, patterns, and web standards (HTTP, +REST, JSON) + +\- Efficient and cost-effective + +\- Work well with SDKs in many programming languages + +\- Sustainable & versionable via clear API contracts . + +The Microsoft Graph guidelines are an extension of the Microsoft REST API +guidelines. Readers are assumed also be reading the Microsoft REST API +guidelines and be familiar with them. Graph guidance is a superset of the +Microsoft API guidelines and services should follow them except where this +document outlines specific differences or exceptions to those guidelines. + +This document borrows heavily from multiple public sources such as: + +1. Microsoft Azure REST API Guidelines + +2. Google Cloud Platform APIs + +3. WSO2 Rest API Design Guidelines and others. + +Technology and software is constantly changing and evolving, and as such, this +is intended to be a living document. [Open an +issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest +a change or propose a new idea. + +## + +## Design Approach + +The design of your API is arguably the most important investment you will make +in it. The design of your API is what creates the first impression for +developers. Microsoft Graph APIs follow HTTP, REST, and JSON standards and +described using ODATA conventions and CSDL for schema definition (see +[Documentation · OData - the Best Way to +REST](https://www.odata.org/documentation/)). + +We promote API-first design approach where you begin by creating an interface or +API for your service first. Subsequently you follow with the service +implementation which relies on the specified interface. API -first approach is +essential for agility, predictability, and reuse of your APIs as it promotes +good understanding of your modeling domain, consistent interface contract, and +understanding of how supporting service will evolve. + +In general API design includes the following steps: + +- Define your domain model + +- Derive and name your API resources + +- Determine required behavior + +- Determine user roles and permissions + +- Specify errors + +To create a good API you need to start with understanding your use cases and +supporting domain model. We describe domain models in terms of entities, their +properties, and relationships and further refer to it as entity data model. +There is no one-to-one correspondence between domain model elements and API +resources as APIs usually support only customer-facing use cases. + +After API resources are identified you need to name them and their properties so +that the API will be discoverable and intuitive for developers, and consistent +with other Graph resources. + +When resources are defined it’s time to think about the behavior of your API and +define required operations and actions. + +At every step of your design you need to consider security, privacy and +compliance as an intrinsic parts of your API implementation. And finally based +on your API resources, their behavior, and anticipated exceptions you need to +identify potential error scenarios with secure and descriptive messaging. + +### Naming + +Consistent naming is foundational for API usability. API resources are typically +described by nouns. You need to consider that resources and property names +appear in API URLs and payloads and should be descriptive and easy to +understand. Therefore you should follow the rules in the table below: + +| ✖ AVOID redundant words in names. | Right: /places/{id}/**type** and /phones/{id}/**number** Wrong: /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | +|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| +| ✖ AVOID using brand names in type or property names. | Right: chat Wrong: teamsChat | +| ✖ AVOID using acronyms or abbreviations unless they are broadly understood. | Right: url or htmlSignature Wrong: msodsUrl or dlp | +| ✔ DO use singular nouns for type names. | Right: address Wrong: addresses | +| ✔ DO use plural nouns for collections (for listing a type or collection properties). | Right: addresses Wrong: address | +| ✔ DO pluralize the noun even when followed by an adjective (a "postpositive"). | Right: passersby or mothersInLaw Wrong: notaryPublics or motherInLaws | +| ✔ DO name property as “email” | Right: email Wrong: mail | + +#### Casing + +| ✔ DO use lower camel case for *all* names and namespaces | Right: automaticRepliesStatus. Wrong: kebab-case or snake_case. | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| +| ✔ DO case two-letter acronyms with the same case. | Right: ioLimit or totalIOAmount Wrong: iOLimit or totalIoAmount | +| ✔ DO case three+ letter acronyms the same as a normal word. | Right: fidoKey or oauthUrl Wrong: webHTML | +| ✖ DO NOT capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | Right: subcategory, geocoordinate or crosswalk Wrong: metaData, semiCircle or airPlane | +| ✔ DO capitalize within hyphenated and open (spaced) compound words. | Right: fiveYearOld, daughterInLaw or postOffice Wrong: paperclip, changingroom or fullmoon | + +#### Prefixes and Suffixes + +| ✔ DO use namespaces | Microsoft Graph model types can be declared within a [type namespaces](https://github.com/microsoft/api-guidelines/blob/graph/graph/type-namespaces) to reduce the need to prefix types with a qualifier to ensure uniqueness. | +|--------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ✔ DO suffix date and time properties with | Right: dueDate — an Edm.Date Right: createdDateTime — an Edm.DateTimeOffset Right: recurringMeetingTime — an Edm.TimeOfDay Wrong: dueOn or startTime Right: instead both above are an Edm.DateTimeOffset | +| ✔ DO use the Duration type for durations, but if using an int, append the units. | Right: passwordValidityPeriod — an Edm.Duration Right: passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable) Wrong: passwordValidityPeriod — an Edm.Int32 | +| ✖ DO NOT suffix property names with primitive type names unless the type is temporal. | Right: isEnabled or amount Wrong: enabledBool | +| ✔ DO prefix property names for properties concerning a different entity. | Right: siteWebUrl on driveItem, or userId on auditActor Wrong: webUrl on contact when its the companyWebUrl | +| ✔ DO prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | • Right: isEnabled or isResourceAccount • Wrong: enabled or allowResourcAccount • Right: allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below • Wrong: isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | + +### Uniform Resource Locators (URLs) + +A Uniform Resource Locator (URL) is how developers access the resources of your +API. + +Navigation path to Graph resources generally broken into multiple segments: + +**{scheme}://{host}/{version}/{category}/{resourcePath}[?{query}]** where + +- **scheme and host segments** are always + [https://graph.microsoft.com](https://graph.microsoft.com/v1.0/users); + +- **version** can be V1.0 or beta; + +- **category** segment is modeled as an entity set or a singleton representing + logical top-level API category; + +- **resourcePath segment** can address an entity, collection of entities, + property or operation available for an entity. Structure of the resource + path is covered in detail in the [OData Version 4.01. Part 2: URL + Conventions](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html); + +- **query string** must follow the OData standard for query representations + and is covered in [Query](#query) section. + +While HTTP defines no constraints on how different resources are related +together, it does encourage the use of URL path segment hierarchies to convey a +relationship. In Microsoft Graph lifetime relationships between resources +supported by the notions of singletons, entitySets, entities, complex types and +navigation properties. + +#### Category + +We define a **top-level API category** as a coherent area of API functionality +which covers one or multiple high-level use cases defined from customer and +enterprise perspectives and represents one of the following: + +1. A core *user-centric concept* of the Graph + +- For example: /users, /groups or /me + +1. A Microsoft *product or service offerings* covering multiple use cases + +- For example: /teamwork, /directory + +1. A *feature* offering covering a single use case and *shared* across multiple + Microsoft products + +- For example: /search, /notifications, /subscriptions, /files + +1. *Administrative configuration* functions for specific products. (Note: this + is not final and may be adjusted based on the survey results) + +- For example: /admin/exchange + +1. Internal Microsoft requirements for publishing Privileged and Hidden APIs, + routing, and load testing + +- For example: /loadTestEntities + +Top-level API categories are aligned with documentation, developer tools, and in +general are relatively stable. If a new category needs to be created, it should +follow supporting governance + +#### Query + +Microsoft Graph APIs should support basic query options in conformance with +OData specifications and [Microsoft REST API +Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). + +| ✔ DO support \$select, \$top, \$filter query options | +|----------------------------------------------------------------------------------------------------| +| ✔ DO support \$filter with eq, ne operations on properties of entities in the requested entity set | +| ✔ may support \$skip, \$count | +| ✔ DO use batch request to avoid too long query options | + +Limitations of \$query requests made to Microsoft Graph: + +- Microsoft Graph only supports having all the query options completely in the + request body or completely in the request url. Graph doesn't support query + options present in both places. + +- The parameters in \$query should not span multiple workloads. Support for + \$query right now is limited to properties belonging to the same workload. + +An easier alternative for GET requests is to append /\$query to the resource +path of the URL, use the POST verb instead of GET, and pass the query options +part of the URL in the request body. The request body MUST use the content-type +text/plain. It contains the query portion of the URL and MUST use the same +percent-encoding as in URLs (especially: no spaces, tabs, or line breaks +allowed) and MUST follow the syntax rules described in chapter Query Options. + +#### Microsoft Graph rules for modeling resources: + +| ✔ DO verify that the primary id of an entity type is string | +|-------------------------------------------------------------------------------------| +| ✔ DO verify that the primary key must also be defined as a property. | +| ✔ DO verify that the primary key is composed of a single property and not multiple. | +| ✖ DO NOT add the property id to a complex type | +| **Serialization** | +| ✔ DO use an object as the root of all JSON payloads. | +| ✔ DO use a value property in the root object to return a collection. | +| ✔ DO include @odata.type annotations when the type is ambiguous. | + +### Recommended Modeling Patterns + +There are different approaches to design an API resource model in situations +with multiple variants of common concept. Type Hierarchy, Facets, and Flat bag +of properties are three most often used patterns in Microsoft Graph today: + +- Type hierarchy is represented by one abstract base type with a few common + properties and one sub-type for each variant + [api-guidelines/adding-subtypes.md at graph · microsoft/api-guidelines + (github.com)](https://github.com/microsoft/api-guidelines/blob/graph/graph/adding-subtypes.md) + +- Facets are represented by a single entity type with common properties and + one facet property (of complex type) per variant. The facet properties only + have a value when the object represents that variant + [api-guidelines/adding-subtypes.md at graph · microsoft/api-guidelines + (github.com)](https://github.com/microsoft/api-guidelines/blob/graph/graph/adding-subtypes.md) + +- Flat bag of properties is represented by one entity type with all the + potential properties plus an additional property to distinguish the + variants, often called type. The type property describes the variant and + also defines properties that are required/meaningful for the variant given + by the type property. [api-guidelines/adding-subtypes.md at graph · + microsoft/api-guidelines + (github.com)](https://github.com/microsoft/api-guidelines/blob/graph/graph/adding-subtypes.md) + +The following table describes shows summary of main qualities for each pattern +and will help to select a pattern preferred for your use case. + +| API qualities Patterns | Properties and behavior described in metadata | Suited for strongly typed languages | Simple query construction | Syntactical backward compatible | +|---------------------------|------------------------------------------------|-------------------------------------|---------------------------|---------------------------------| +| Type hierarchy | yes | yes | no | yes | +| Facets | ok | ok | yes | yes | +| Flat bag | no | no | yes | yes | + +## Behavior Modeling + +#### HTTP Operations + +The HTTP operations dictate how your API behaves. The URL of an API, along with +its request/response bodies, establishes the overall contract that developers +have with your service. As an API provider, how you manage the overall request / +response pattern should be one of the first implementation decisions you make. + +| ✔ DO use POST to create new entities in insertable entity sets | +|------------------------------------------------------------------------------------| +| ✔ DO use PATCH to edit updatable resources | +| ✔ DO use DELETE to delete deletable resources | +| ✔ DO return a Location header with the edit URL or read URL of a created resource | + +For a complete list of standard REST operations you can refer to the [Microsoft +REST API +Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). + +#### Microsoft Graph rules for modeling behavior: + +| ✔ DO use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | Error | +|--------------------------------------------------------------------------------------------|---------| +| ✔ DO use POST …/{collection} for creating resources. | Error | +| ✔ DO use PATCH …/{collection}/{id} for updating resources. | Error | +| ✖ AVOID using PUT …/{collection}/{id} for updating resources. | Warning | +| ✖ DO NOT use PATCH to replaces resources or PUT to partially update resources. | Error | +| ✖ AVOID patterns that require multiple round trips to complete a single logical action. | Warning | +| ✔ CONSIDER supporting return and omit-nulls preferences. | Warning | + +### Error Handling + +Microsoft REST API Guidelines provide guidelines that Microsoft REST APIs should +follow when returning error condition responses. However, the structure, form +and content of the error response payloads is currently not enforced leading to +undiscoverable and inconsistent error messages. You can improve API traceability +and consistency by using recommended Graph error model: + +{ + +"error": { + +"code": "BadRequest", + +"message": "Unsupported functionality", + +"target": "query", + +"details": [ + +{ + +"code": "301", + +"target": "\$search", + +"message": "\$search query option not supported" + +} + +], + +"innererror": { + +"code": "301", + +"message": "Cannot process the request because a required field is missing.", + +"stacktrace": [...], + +} + +} + +} + +The following examples demonstrate error modeling for common use cases: + +- **Simple error**: A workload wants to report an error with top-level details + only. The library allows the workload to create the error object and just + specify the top-level error code, message and target (optional). + +{ + +"error": { + +"code": "badRequest", + +"message": "Cannot process the request because it is malformed or incorrect.", + +"target": "Service X (Optional)" + +} + +} + +- **Detailed error**: A workload wants to report an error and provide + service-specific details of the error via the innererror property of the + error object. The code property in innererror is optional but highly + recommended. It is intended to allow workloads to supply a service-specific + error code to help differentiate errors that share the same top-level error + code but reported for different reasons. + +{ + +"error": { + +"code": "badRequest", + +"message": "Cannot process the request because it is malformed or incorrect.", + +"innererror": { + +"code": "requiredFieldOrParameterMissing", + +"message": "Cannot process the request because a required field is missing.", + +"stacktrace": "[StackTrace]" + +} + +} + +} + +- **Error with collection of related errors**: A workload wants to report an + error together with a collection of related errors via the details + collection property of the error object. + +{ + +"error": { + +"code": "forbidden", + +"message": "Access to the resource is restricted.", + +"details": [ + +{ + +"code": "unathorized", + +"message": "You are not authorized to access the resource" + +} + +] + +} + +} + +#### Microsoft Graph enforces the list of following error rules: + +| ✔ DO return an error property with a child code property in all error responses. | Error | +|---------------------------------------------------------------------------------------------|---------| +| ✔ DO return a 403 Forbidden error when insufficient scopes are present on the auth token. | Error | +| ✔ CONSIDER returning a 404 Not found error if a 403 would result in information disclosure. | Warning | +| ✔ DO return a 429 Too many requests error when the caller has exceeded throttling limits. | Error | + +For a complete mapping of error codes to HTTP statuses please refer to the +[Appendix 3: Top-level error code to HTTP status +mapping](#_Appendix_3:_Top-level). + +The following table shows the mapping between the top-level error codes and +their corresponding HTTP status codes. The list comprises of a subset of the +[HTTP status codes](https://datatracker.ietf.org/doc/html/rfc7231#section-6.5) +for typical error scenarios - 4xx and 5xx. Important to note also is that the +top-level error codes are derived from the documented reason phrases +corresponding to each HTTP status code. + +Graph lib error framework - Overview (azure.com) + +### API contract and non-backward compatible changes + +In general, making changes to existing elements, or removing existing elements +is considered breaking. Adding new elements is allowed and not considered +breaking change refer to [Microsoft REST API +Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change) + +| ✔ DO use **not-breaking** changes | Addition of an annotation OpenType="true" Addition of properties that are nullable or have a default value Addition of a member to an evolvable enumeration Removal, rename, or change to the type of an open extension Removal, rename, or change to the type of an annotation Introduction of paging to existing collections Changes to error codes Changes to the order of properties Changes to the length or format of opaque strings, such as resource IDs | +|-----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ✖ DO NOT use **breaking** changes | Changes to the URL or fundamental request/response associated with a resource Changing semantics of resource representation Removal, rename, or change to the type of a declared property Removal or rename of APIs or API parameters Addition of a required request header Addition of a EnumType members for non-extensible enumerations Addition of a Nullable="false" properties to existing types Addition of a Nullable="false" parameters to existing actions and functions Adding attributes to existing nodes is considered breaking. Adding annotations ags:IsHidden="true". | + +## Versioning and Deprecation + +All APIs compliant with the Microsoft REST API Guidelines MUST support explicit +versioning. It's critical that clients can count on services to be stable over +time, and it's critical that services can add features and make changes. + +Microsoft Graph API follows the guidance described in the Model Versioning +section in the [Microsoft REST API +guidelines](https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#12-versioning) + +API deprecation process - Overview (azure.com) + +As new versions of the Microsoft Graph REST APIs and Microsoft Graph SDKs are +released, earlier versions will be retired. Microsoft declares a version as +deprecated at least 24 months in advance of retiring it. Similarly, for +individual APIs that are generally available (GA), Microsoft declares an API as +deprecated at least 24 months in advance of removing it from the GA version. + +When we increment the major version of the API (for example, from v1.0 to v2.0), +we are announcing that the current version (in this example, v1.0) is +immediately deprecated and we will no longer support it 24 months after the +announcement. We might make exceptions to this policy for service security or +health reliability issues. + +When an API is marked as deprecated, we strongly recommend that you migrate to +the latest version as soon as possible. In some cases, we will announce that new +applications will have to start using the new APIs a short time after the +original APIs are deprecated. In those cases, only active applications that +currently use the deprecated APIs can continue to use them. + +The following versions of the Microsoft Graph API are currently available. + +#### Beta version + +In general, APIs debut in the beta version and are accessible in the +https://graph.microsoft.com/beta endpoint. For beta API documentation, see +Microsoft Graph beta endpoint reference. Expect breaking changes and deprecation +of APIs in the beta version from time to time. Do not take a production +dependency on beta APIs. + +We make no guarantees that a beta feature will be promoted to the current +version. When the Microsoft Graph API team believes that a beta feature is ready +for general availability, we will add that feature to the latest current +version. If the promotion of the feature would result in a breaking change to +the current version, the version number will be incremented, with the new +version becoming the current version. Our developer community can post feature +request on UserVoice, including requests for new features as well as requests to +promote existing beta APIs to the current version. + +#### Current version + +The current version of Microsoft Graph is v1.0. Exposed under +https://graph.microsoft.com/v1.0, the Microsoft Graph API v1.0 version contains +features that are generally available and ready for production use. Browse the +documentation for the v1.0 APIs. + +## Common API Patterns + +The guidelines in previous sections are intentionally high-level and provide a +jump start for Graph API design. More detailed design guidance on REST APIs is +published at the [Microsoft REST API +Guidelines](https://github.com/microsoft/api-guidelines/) and Graph specific are +outlined in the table below. + +**API Patterns** are design documents providing best practices for MS Graph API +development. + +They are to serve as the source of truth for API-related documentation at +Microsoft and the means by which API teams discuss and come to consensus on API +guidance. + +You can find ….The table below provides reference for the existing Graph API +patterns: + +Use the following table for a more detailed discussion of REST API design +patterns. + +| Pattern | Description | Reference | +|-------------------------|-------------|----------------------------------------------------------------------------------------------------------| +| Key Property | | [Key Property](https://dev.azure.com/msazure/One/_wiki/wikis/Microsoft%20Graph%20Partners/103125/Design) | +| Entity Type | | | +| Complex Type | | | +| Shared Type | | | +| Type Hierarchy | | | +| Dictionary | | | +| Evolvable Enums | | | +| Type Namespace | | | +| Change Tracking | | | +| Long Running Operations | | | +| Delta Queries | | | + +These patterns are provided as instruction for API desiners to help write +simple, intuitive, and consistent APIs, and are used by API reviewers as a basis +for review comments. + +## Final thoughts + +These guidelines describe the upfront design considerations, technology building +blocks, and common patterns that teams encounter when building their Graph APIs. + +The links below provide references to the foundational documentation on related +topics: + +- [Microsoft REST API + Guidelines](https://github.com/microsoft/api-guidelines/) + +- [OData Guidelines](http://www.odata.org/documentation/) + +- [RESTful web API + design](https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design) + +- [Microsoft Graph + Documentation](https://developer.microsoft.com/en-us/graph/docs/concepts/overview) + +- [Microsoft Graph Explorer](https://aka.ms/ge) + + diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index 5b05f4b8..eafd7371 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -73,27 +73,24 @@ addition, you can follow some of the mitigation techniques such as: - Avoid overgeneralized base types - Think about roll-out sequence - ->>>Consider that Microsoft Graph does not return objects from a workload ->>>that has a type that is not configured in current metadata. To avoid ->>>inconsistencies, follow a two-step process: - ->>>Introduce the entity type to the Graph metadata but don’t + - Consider that Microsoft Graph does not return objects from a workload +that has a type that is not configured in current metadata. To avoid +inconsistencies, follow a two-step process: + - Introduce the entity type to the Graph metadata but don’t return objects of the type in any of the heterogeneous collections. - ->>> Enable your workload to return objects of the new type as items + - Enable your workload to return objects of the new type as items of collection. -- Allow time for testing ->>>Inform the clients about the change and allow them to test the +- Allow time for testing + - Inform the clients about the change and allow them to test the changes in beta. Time is required to implement the code necessary to deal with the new entity type, both in terms of de-serialization as well as integrating it into the rest of the application. - Communicate the change in semantics ->>>It is necessary for the client developers to incorporate the new + - It is necessary for the client developers to incorporate the new semantic into their application/service, even if the change is perceived to be small. This requires early communication and clear documentation of what the new type represents and why/how it is considered a subtype From 65c5b660039fcbbb56f9aa5fdd771d44ec108f19 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Sun, 7 Nov 2021 19:24:22 -0500 Subject: [PATCH 182/729] format testing --- graph/GuidelinesGraph.md | 231 ++++++++++++++++++++++++++------------- 1 file changed, 155 insertions(+), 76 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 344c8e8e..f86f030f 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -2,17 +2,17 @@ Table of Contents -[Microsoft Graph REST API Guidelines](#_Toc86861191) +[Microsoft Graph REST API Guidelines](#_Toc87203511) -[Introduction](#_Toc86861192) +[Introduction](#_Toc87203512) [Design Approach](#design-approach) -[Naming](#_Toc86861194) +[Naming](#_Toc87203514) [Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) -[Recommended Modeling Patterns](#_Toc86861196) +[Recommended Modeling Patterns](#_Toc87203516) [Behavior Modeling](#behavior-modeling) @@ -23,12 +23,27 @@ changes](#api-contract-and-non-backward-compatible-changes) [Versioning and Deprecation](#versioning-and-deprecation) +[Deprecation Process](#deprecation-process) + [Common API Patterns](#common-api-patterns) [Final thoughts](#final-thoughts) +[[_TOC_]] + ## +### Prescriptive Guidance +This document offers prescriptive guidance labeled as follows: + +:white_check_mark: **DO** fulfill this specification. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. + +:ballot_box_with_check: **YOU SHOULD** fulfill this specification. If not following this advice, you MUST disclose your reason during the Graph API review. + +:heavy_check_mark: **YOU MAY** satisfy this specification if appropriate to your situation. No notification to the Graph API Review board is required. +:warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Graph API Review board review. + +:no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. #### History | Date | Notes | @@ -49,11 +64,11 @@ following goals: \- Developer friendly via consistent naming, patterns, and web standards (HTTP, REST, JSON) -\- Efficient and cost-effective +\- Efficient and cost-effective. -\- Work well with SDKs in many programming languages +\- Work well with SDKs in many programming languages. -\- Sustainable & versionable via clear API contracts . +\- Sustainable & versionable via clear API contracts. The Microsoft Graph guidelines are an extension of the Microsoft REST API guidelines. Readers are assumed also be reading the Microsoft REST API @@ -232,6 +247,8 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m | ✔ DO support \$filter with eq, ne operations on properties of entities in the requested entity set | | ✔ may support \$skip, \$count | | ✔ DO use batch request to avoid too long query options | +| ✔ DO use request body with the content-type text/plain for POST queries | +| ✔ DO use request body with the content-type | Limitations of \$query requests made to Microsoft Graph: @@ -242,12 +259,15 @@ Limitations of \$query requests made to Microsoft Graph: - The parameters in \$query should not span multiple workloads. Support for \$query right now is limited to properties belonging to the same workload. -An easier alternative for GET requests is to append /\$query to the resource -path of the URL, use the POST verb instead of GET, and pass the query options -part of the URL in the request body. The request body MUST use the content-type -text/plain. It contains the query portion of the URL and MUST use the same -percent-encoding as in URLs (especially: no spaces, tabs, or line breaks -allowed) and MUST follow the syntax rules described in chapter Query Options. +The query options part of an OData URL can be quite long, potentially exceeding +the maximum length of URLs supported by components involved in transmitting or +processing the request. One way to avoid this is wrapping the request in a batch +request, which has the penalty of needing to construct a well-formed batch +request body. An easier alternative for GET requests is to append /\$query to +the resource path of the URL, use the POST verb instead of GET, and pass the +query options part of the URL in the request body as described in the chapter +[OData Query +Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). #### Microsoft Graph rules for modeling resources: @@ -457,40 +477,48 @@ The following examples demonstrate error modeling for common use cases: | ✔ DO return a 429 Too many requests error when the caller has exceeded throttling limits. | Error | For a complete mapping of error codes to HTTP statuses please refer to the -[Appendix 3: Top-level error code to HTTP status -mapping](#_Appendix_3:_Top-level). +[rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). -The following table shows the mapping between the top-level error codes and -their corresponding HTTP status codes. The list comprises of a subset of the -[HTTP status codes](https://datatracker.ietf.org/doc/html/rfc7231#section-6.5) -for typical error scenarios - 4xx and 5xx. Important to note also is that the -top-level error codes are derived from the documented reason phrases -corresponding to each HTTP status code. +### API contract and non-backward compatible changes -Graph lib error framework - Overview (azure.com) +Microsoft Graph definition of breaking changes is based on the [Microsoft REST +API +Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change). -### API contract and non-backward compatible changes +In general, making changes to the API contract for existing elements is +considered breaking. Adding new elements is allowed and not considered a +breaking change. -In general, making changes to existing elements, or removing existing elements -is considered breaking. Adding new elements is allowed and not considered -breaking change refer to [Microsoft REST API -Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change) +Additional Microsoft Graph rules most often observed in practice are summarized +in the table below: | ✔ DO use **not-breaking** changes | Addition of an annotation OpenType="true" Addition of properties that are nullable or have a default value Addition of a member to an evolvable enumeration Removal, rename, or change to the type of an open extension Removal, rename, or change to the type of an annotation Introduction of paging to existing collections Changes to error codes Changes to the order of properties Changes to the length or format of opaque strings, such as resource IDs | |-----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ✖ DO NOT use **breaking** changes | Changes to the URL or fundamental request/response associated with a resource Changing semantics of resource representation Removal, rename, or change to the type of a declared property Removal or rename of APIs or API parameters Addition of a required request header Addition of a EnumType members for non-extensible enumerations Addition of a Nullable="false" properties to existing types Addition of a Nullable="false" parameters to existing actions and functions Adding attributes to existing nodes is considered breaking. Adding annotations ags:IsHidden="true". | +For the full list of rules you can refer to [this section of the OData V4 +spec](https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209). + ## Versioning and Deprecation -All APIs compliant with the Microsoft REST API Guidelines MUST support explicit -versioning. It's critical that clients can count on services to be stable over -time, and it's critical that services can add features and make changes. +When changes are imminent you need to support explicit versioning as it's +critical that clients can count on services to be stable over time, and it's +critical that services can add features and make changes. Microsoft Graph API +follows the guidance described in the Model Versioning section in the [Microsoft +REST API +guidelines](https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#12-versioning). + +The following versions of the Microsoft Graph API are currently available: + +1. API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in + general availability (GA) status. -Microsoft Graph API follows the guidance described in the Model Versioning -section in the [Microsoft REST API -guidelines](https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#12-versioning) +2. API sets on the beta endpoint (https://graph.microsoft.com/beta) are in beta + or private preview status. -API deprecation process - Overview (azure.com) +In general API breaking changes are not allowed in the GA version of Microsoft +Graph API. For beta API you can expect breaking changes and deprecation of APIs +from time to time. As new versions of the Microsoft Graph REST APIs and Microsoft Graph SDKs are released, earlier versions will be retired. Microsoft declares a version as @@ -498,43 +526,103 @@ deprecated at least 24 months in advance of retiring it. Similarly, for individual APIs that are generally available (GA), Microsoft declares an API as deprecated at least 24 months in advance of removing it from the GA version. -When we increment the major version of the API (for example, from v1.0 to v2.0), -we are announcing that the current version (in this example, v1.0) is -immediately deprecated and we will no longer support it 24 months after the -announcement. We might make exceptions to this policy for service security or -health reliability issues. +### Deprecation Process + +If your API requires an introduction of breaking changes you must follow the +deprecation process: + +- After API review board approvals, add Revisions annotation to the API + definition CSDL with the following terms: + + - Kind of change: Deprecated (vs "added" to track added properties/types) + + - Human readable description of the change: Used in changelog, + documentation etc. + + - Version: Used to identify group of changes. Of the format + "YYYY-MM/Category" where "YYYY-MM" is the month the deprecation is + announced, and "Category" is the category under which the change is + described in the ChangeLog + + - Date: Date when the element was marked as deprecated + + - RemovalDate: Date when the element may be removed + +The annotation can be applied to a type, entity set, singleton, property, +navigation property, function or action. If a type is marked as deprecated, it +is not necessary to mark members of that type as deprecated, nor is it necessary +to annotate any usage of that type in entity sets, singletons, properties, +navigation properties, functions, or actions. -When an API is marked as deprecated, we strongly recommend that you migrate to -the latest version as soon as possible. In some cases, we will announce that new -applications will have to start using the new APIs a short time after the -original APIs are deprecated. In those cases, only active applications that -currently use the deprecated APIs can continue to use them. +Example of property annotation: -The following versions of the Microsoft Graph API are currently available. +\ -#### Beta version +\ -In general, APIs debut in the beta version and are accessible in the -https://graph.microsoft.com/beta endpoint. For beta API documentation, see -Microsoft Graph beta endpoint reference. Expect breaking changes and deprecation -of APIs in the beta version from time to time. Do not take a production -dependency on beta APIs. +\ -We make no guarantees that a beta feature will be promoted to the current -version. When the Microsoft Graph API team believes that a beta feature is ready -for general availability, we will add that feature to the latest current -version. If the promotion of the feature would result in a breaking change to -the current version, the version number will be incremented, with the new -version becoming the current version. Our developer community can post feature -request on UserVoice, including requests for new features as well as requests to -promote existing beta APIs to the current version. +\ -#### Current version +\ -The current version of Microsoft Graph is v1.0. Exposed under -https://graph.microsoft.com/v1.0, the Microsoft Graph API v1.0 version contains -features that are generally available and ready for production use. Browse the -documentation for the v1.0 APIs. +\ + +\ + +\ + +\ + +\ + +\ + +\ + +... + +\ + +When the request URL contains a reference to a deprecated model element, the +HTTP response includes a [Deprecation +header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the +date the element was marked as deprecated) and a Sunset header (with the date 2 +years beyond the Deprecation date). Response also includes a link header +pointing to the breaking changes page. + +Deprecation header example: + +*Deprecation: Thursday, 30 June 2022 11:59:59 GMT +Sunset: Wed, 30 Mar 2022 23:59:59 GMT +Link:* +[*https://docs.microsoft.com/en-us/graph/changelog\#2022-03-30_name*](https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name) +*; rel="deprecation"; type="text/html"; +title="name",*[*https://docs.microsoft.com/en-us/graph/changelog\#2020-06-30_state*](https://docs.microsoft.com/en-us/graph/changelog#2020-06-30_state) +*; rel="deprecation"; type="text/html"; title="state"* + +Deprecation cadence: + +- As an API developer you can mark individual API schema elements as + deprecated on a quarterly basis, after going through a API review and + approval process. Quarterly deprecation cadence will allow the services to + evolve schemas over time, without waiting for a coordinated, monolithic + endpoint change. + +- Once marked as deprecated, the elements must continue to be supported for a + minimum of 3 years before removal (or a minimum of 2 years if, based on + telemetry, the element is no longer being used). + +- Tools, documentation, SDKs, and other mechanisms are driven by this explicit + deprecation to reach out to customers that may be affected by the changes. + +- APIs in beta or preview versions can use the same mechanism but are not + bound by the quarterly cadence or minimal support period before removal of + deprecated elements. ## Common API Patterns @@ -545,17 +633,10 @@ Guidelines](https://github.com/microsoft/api-guidelines/) and Graph specific are outlined in the table below. **API Patterns** are design documents providing best practices for MS Graph API -development. - -They are to serve as the source of truth for API-related documentation at -Microsoft and the means by which API teams discuss and come to consensus on API -guidance. +development. They are to serve as the means by which API teams discuss and come +to consensus on API guidance. -You can find ….The table below provides reference for the existing Graph API -patterns: - -Use the following table for a more detailed discussion of REST API design -patterns. +You can find references in the most common patterns in the table below: | Pattern | Description | Reference | |-------------------------|-------------|----------------------------------------------------------------------------------------------------------| @@ -595,5 +676,3 @@ topics: Documentation](https://developer.microsoft.com/en-us/graph/docs/concepts/overview) - [Microsoft Graph Explorer](https://aka.ms/ge) - - From effc296dba44f126fb0e33a0842c4fb0bedd0340 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 8 Nov 2021 08:41:43 -0500 Subject: [PATCH 183/729] formatting and TOC --- graph/GuidelinesGraph.docx | Bin 0 -> 56825 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 graph/GuidelinesGraph.docx diff --git a/graph/GuidelinesGraph.docx b/graph/GuidelinesGraph.docx new file mode 100644 index 0000000000000000000000000000000000000000..cd4483bdcc92867b73af4cd6df4a97b4cb1717a7 GIT binary patch literal 56825 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
  • !6cZSn9=b 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 A!T Date: Mon, 8 Nov 2021 11:46:34 -0500 Subject: [PATCH 184/729] formatting --- graph/GuidelinesGraph.md | 70 ++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index f86f030f..94bed50e 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -143,9 +143,9 @@ Consistent naming is foundational for API usability. API resources are typically described by nouns. You need to consider that resources and property names appear in API URLs and payloads and should be descriptive and easy to understand. Therefore you should follow the rules in the table below: - -| ✖ AVOID redundant words in names. | Right: /places/{id}/**type** and /phones/{id}/**number** Wrong: /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | +|Requirements|Example| |--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| +| ✖ AVOID redundant words in names. | Right: /places/{id}/**type** and /phones/{id}/**number** Wrong: /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | | ✖ AVOID using brand names in type or property names. | Right: chat Wrong: teamsChat | | ✖ AVOID using acronyms or abbreviations unless they are broadly understood. | Right: url or htmlSignature Wrong: msodsUrl or dlp | | ✔ DO use singular nouns for type names. | Right: address Wrong: addresses | @@ -154,9 +154,9 @@ understand. Therefore you should follow the rules in the table below: | ✔ DO name property as “email” | Right: email Wrong: mail | #### Casing - +|Requirements|Example| +|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| | ✔ DO use lower camel case for *all* names and namespaces | Right: automaticRepliesStatus. Wrong: kebab-case or snake_case. | -|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| | ✔ DO case two-letter acronyms with the same case. | Right: ioLimit or totalIOAmount Wrong: iOLimit or totalIoAmount | | ✔ DO case three+ letter acronyms the same as a normal word. | Right: fidoKey or oauthUrl Wrong: webHTML | | ✖ DO NOT capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | Right: subcategory, geocoordinate or crosswalk Wrong: metaData, semiCircle or airPlane | @@ -165,8 +165,9 @@ understand. Therefore you should follow the rules in the table below: #### Prefixes and Suffixes | ✔ DO use namespaces | Microsoft Graph model types can be declared within a [type namespaces](https://github.com/microsoft/api-guidelines/blob/graph/graph/type-namespaces) to reduce the need to prefix types with a qualifier to ensure uniqueness. | -|--------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ✔ DO suffix date and time properties with | Right: dueDate — an Edm.Date Right: createdDateTime — an Edm.DateTimeOffset Right: recurringMeetingTime — an Edm.TimeOfDay Wrong: dueOn or startTime Right: instead both above are an Edm.DateTimeOffset | +|Requirements|Example| +|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| +| ✔ DO suffix date and time properties with | Right: dueDate — an Edm.Date Right: createdDateTime — an Edm.DateTimeOffset Right: recurringMeetingTime — an Edm.TimeOfDay Wrong: dueOn or startTime Right: instead both above are an Edm.DateTimeOffset | | ✔ DO use the Duration type for durations, but if using an int, append the units. | Right: passwordValidityPeriod — an Edm.Duration Right: passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable) Wrong: passwordValidityPeriod — an Edm.Int32 | | ✖ DO NOT suffix property names with primitive type names unless the type is temporal. | Right: isEnabled or amount Wrong: enabledBool | | ✔ DO prefix property names for properties concerning a different entity. | Right: siteWebUrl on driveItem, or userId on auditActor Wrong: webUrl on contact when its the companyWebUrl | @@ -241,9 +242,9 @@ follow supporting governance Microsoft Graph APIs should support basic query options in conformance with OData specifications and [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). - -| ✔ DO support \$select, \$top, \$filter query options | +|Requirements| |----------------------------------------------------------------------------------------------------| +| ✔ DO support \$select, \$top, \$filter query options | | ✔ DO support \$filter with eq, ne operations on properties of entities in the requested entity set | | ✔ may support \$skip, \$count | | ✔ DO use batch request to avoid too long query options | @@ -269,10 +270,9 @@ query options part of the URL in the request body as described in the chapter [OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). -#### Microsoft Graph rules for modeling resources: - -| ✔ DO verify that the primary id of an entity type is string | +| Microsoft Graph rules for modeling resources| |-------------------------------------------------------------------------------------| +| ✔ DO verify that the primary id of an entity type is string | | ✔ DO verify that the primary key must also be defined as a property. | | ✔ DO verify that the primary key is composed of a single property and not multiple. | | ✖ DO NOT add the property id to a complex type | @@ -352,7 +352,7 @@ follow when returning error condition responses. However, the structure, form and content of the error response payloads is currently not enforced leading to undiscoverable and inconsistent error messages. You can improve API traceability and consistency by using recommended Graph error model: - +``` { "error": { @@ -390,13 +390,13 @@ and consistency by using recommended Graph error model: } } - +``` The following examples demonstrate error modeling for common use cases: - **Simple error**: A workload wants to report an error with top-level details only. The library allows the workload to create the error object and just specify the top-level error code, message and target (optional). - +``` { "error": { @@ -467,7 +467,7 @@ The following examples demonstrate error modeling for common use cases: } } - +``` #### Microsoft Graph enforces the list of following error rules: | ✔ DO return an error property with a child code property in all error responses. | Error | @@ -492,9 +492,26 @@ breaking change. Additional Microsoft Graph rules most often observed in practice are summarized in the table below: -| ✔ DO use **not-breaking** changes | Addition of an annotation OpenType="true" Addition of properties that are nullable or have a default value Addition of a member to an evolvable enumeration Removal, rename, or change to the type of an open extension Removal, rename, or change to the type of an annotation Introduction of paging to existing collections Changes to error codes Changes to the order of properties Changes to the length or format of opaque strings, such as resource IDs | -|-----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ✖ DO NOT use **breaking** changes | Changes to the URL or fundamental request/response associated with a resource Changing semantics of resource representation Removal, rename, or change to the type of a declared property Removal or rename of APIs or API parameters Addition of a required request header Addition of a EnumType members for non-extensible enumerations Addition of a Nullable="false" properties to existing types Addition of a Nullable="false" parameters to existing actions and functions Adding attributes to existing nodes is considered breaking. Adding annotations ags:IsHidden="true". | +| ✔ DO use **not-breaking** changes:| + |-----------------------------------| +* Addition of an annotation OpenType="true" Addition of properties that are nullable or have a default value +* Addition of a member to an evolvable enumeration 1. Removal, rename, or change to the type of an open extension +* Removal, rename, or change to the type of an annotation *Introduction of paging to existing collections +* Changes to error codes Changes to the order of properties +* Changes to the length or format of opaque strings, such as resource IDs + + +| ✖ DO NOT use **breaking** changes | +|-----------------------------------| +* Changes to the URL or fundamental request/response associated with a resource +* Changing semantics of resource representation +* Removal, rename, or change to the type of a declared property +* Removal or rename of APIs or API parameters Addition of a required request header +* Addition of a EnumType members for non-extensible enumerations +* Addition of a Nullable="false" properties to existing types +* Addition of a Nullable="false" parameters to existing actions and functions +* Adding attributes to existing nodes is considered breaking. +* Adding annotations ags:IsHidden="true". | For the full list of rules you can refer to [this section of the OData V4 spec](https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209). @@ -555,7 +572,7 @@ to annotate any usage of that type in entity sets, singletons, properties, navigation properties, functions, or actions. Example of property annotation: - +``` \ @@ -587,7 +604,7 @@ To Do API."/\> ... \ - +``` When the request URL contains a reference to a deprecated model element, the HTTP response includes a [Deprecation header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the @@ -596,15 +613,12 @@ years beyond the Deprecation date). Response also includes a link header pointing to the breaking changes page. Deprecation header example: +``` +Deprecation: Thursday, 30 June 2022 11:59:59 GMT +Sunset: Wed, 30 Mar 2022 23:59:59 GMT +Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2020-06-30_state ; rel="deprecation"; type="text/html"; title="state" -*Deprecation: Thursday, 30 June 2022 11:59:59 GMT -Sunset: Wed, 30 Mar 2022 23:59:59 GMT -Link:* -[*https://docs.microsoft.com/en-us/graph/changelog\#2022-03-30_name*](https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name) -*; rel="deprecation"; type="text/html"; -title="name",*[*https://docs.microsoft.com/en-us/graph/changelog\#2020-06-30_state*](https://docs.microsoft.com/en-us/graph/changelog#2020-06-30_state) -*; rel="deprecation"; type="text/html"; title="state"* - +``` Deprecation cadence: - As an API developer you can mark individual API schema elements as From 075dfebca11286ceb14ce60b3e452c1388d0d9ff Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 8 Nov 2021 11:51:18 -0500 Subject: [PATCH 185/729] Legend --- graph/GuidelinesGraph.md | 51 +++++++++------------------------------- 1 file changed, 11 insertions(+), 40 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 94bed50e..bfe92095 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -2,48 +2,9 @@ Table of Contents -[Microsoft Graph REST API Guidelines](#_Toc87203511) - -[Introduction](#_Toc87203512) - -[Design Approach](#design-approach) - -[Naming](#_Toc87203514) - -[Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) - -[Recommended Modeling Patterns](#_Toc87203516) - -[Behavior Modeling](#behavior-modeling) - -[Error Handling](#error-handling) - -[API contract and non-backward compatible -changes](#api-contract-and-non-backward-compatible-changes) - -[Versioning and Deprecation](#versioning-and-deprecation) - -[Deprecation Process](#deprecation-process) - -[Common API Patterns](#common-api-patterns) - -[Final thoughts](#final-thoughts) - [[_TOC_]] ## -### Prescriptive Guidance -This document offers prescriptive guidance labeled as follows: - -:white_check_mark: **DO** fulfill this specification. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. - -:ballot_box_with_check: **YOU SHOULD** fulfill this specification. If not following this advice, you MUST disclose your reason during the Graph API review. - -:heavy_check_mark: **YOU MAY** satisfy this specification if appropriate to your situation. No notification to the Graph API Review board is required. - -:warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Graph API Review board review. - -:no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. #### History | Date | Notes | @@ -89,8 +50,18 @@ is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. -## +### Legend +This document offers prescriptive guidance labeled as follows: +:white_check_mark: **DO** fulfill this specification. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. + +:ballot_box_with_check: **YOU SHOULD** fulfill this specification. If not following this advice, you MUST disclose your reason during the Graph API review. + +:heavy_check_mark: **YOU MAY** satisfy this specification if appropriate to your situation. No notification to the Graph API Review board is required. + +:warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Graph API Review board review. + +:no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. ## Design Approach The design of your API is arguably the most important investment you will make From 5b9db04c9b1c35f574876c30efe1b8ad90da826a Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 8 Nov 2021 16:36:24 -0500 Subject: [PATCH 186/729] Formatting with icons --- graph/GuidelinesGraph.md | 283 ++++++++++++++------------------------- 1 file changed, 101 insertions(+), 182 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index bfe92095..ab3f1cbb 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -53,15 +53,12 @@ a change or propose a new idea. ### Legend This document offers prescriptive guidance labeled as follows: -:white_check_mark: **DO** fulfill this specification. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. +:heavy_check_mark: **DO** satisfy this specification if appropriate to your situation. No notification to the Graph API Review board is required. -:ballot_box_with_check: **YOU SHOULD** fulfill this specification. If not following this advice, you MUST disclose your reason during the Graph API review. +:no_entry: **AVOID** using this pattern. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. -:heavy_check_mark: **YOU MAY** satisfy this specification if appropriate to your situation. No notification to the Graph API Review board is required. -:warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Graph API Review board review. -:no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. ## Design Approach The design of your API is arguably the most important investment you will make @@ -116,33 +113,32 @@ appear in API URLs and payloads and should be descriptive and easy to understand. Therefore you should follow the rules in the table below: |Requirements|Example| |--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| -| ✖ AVOID redundant words in names. | Right: /places/{id}/**type** and /phones/{id}/**number** Wrong: /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | -| ✖ AVOID using brand names in type or property names. | Right: chat Wrong: teamsChat | -| ✖ AVOID using acronyms or abbreviations unless they are broadly understood. | Right: url or htmlSignature Wrong: msodsUrl or dlp | -| ✔ DO use singular nouns for type names. | Right: address Wrong: addresses | -| ✔ DO use plural nouns for collections (for listing a type or collection properties). | Right: addresses Wrong: address | -| ✔ DO pluralize the noun even when followed by an adjective (a "postpositive"). | Right: passersby or mothersInLaw Wrong: notaryPublics or motherInLaws | -| ✔ DO name property as “email” | Right: email Wrong: mail | +| :no_entry: **AVOID** redundant words in names. | Right: /places/{id}/**type** and /phones/{id}/**number** Wrong: /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | +| :no_entry: **AVOID** using brand names in type or property names. | Right: chat Wrong: teamsChat | +| :no_entry: **AVOID** using acronyms or abbreviations unless they are broadly understood. | Right: url or htmlSignature Wrong: msodsUrl or dlp | +| :heavy_check_mark: **DO** use singular nouns for type names. | Right: address Wrong: addresses | +| :heavy_check_mark: **DO** use plural nouns for collections (for listing a type or collection properties). | Right: addresses Wrong: address | +| :heavy_check_mark: **DO** pluralize the noun even when followed by an adjective (a "postpositive"). | Right: passersby or mothersInLaw Wrong: notaryPublics or motherInLaws | +| :heavy_check_mark: **DO** name property as “email” | Right: email Wrong: mail | #### Casing |Requirements|Example| |--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| -| ✔ DO use lower camel case for *all* names and namespaces | Right: automaticRepliesStatus. Wrong: kebab-case or snake_case. | -| ✔ DO case two-letter acronyms with the same case. | Right: ioLimit or totalIOAmount Wrong: iOLimit or totalIoAmount | -| ✔ DO case three+ letter acronyms the same as a normal word. | Right: fidoKey or oauthUrl Wrong: webHTML | +| :heavy_check_mark: **DO** use lower camel case for *all* names and namespaces | Right: automaticRepliesStatus. Wrong: kebab-case or snake_case. | +| :heavy_check_mark: **DO** case two-letter acronyms with the same case. | Right: ioLimit or totalIOAmount Wrong: iOLimit or totalIoAmount | +| :heavy_check_mark: **DO** case three+ letter acronyms the same as a normal word. | Right: fidoKey or oauthUrl Wrong: webHTML | | ✖ DO NOT capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | Right: subcategory, geocoordinate or crosswalk Wrong: metaData, semiCircle or airPlane | -| ✔ DO capitalize within hyphenated and open (spaced) compound words. | Right: fiveYearOld, daughterInLaw or postOffice Wrong: paperclip, changingroom or fullmoon | +| :heavy_check_mark: **DO** capitalize within hyphenated and open (spaced) compound words. | Right: fiveYearOld, daughterInLaw or postOffice Wrong: paperclip, changingroom or fullmoon | #### Prefixes and Suffixes - -| ✔ DO use namespaces | Microsoft Graph model types can be declared within a [type namespaces](https://github.com/microsoft/api-guidelines/blob/graph/graph/type-namespaces) to reduce the need to prefix types with a qualifier to ensure uniqueness. | |Requirements|Example| -|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| -| ✔ DO suffix date and time properties with | Right: dueDate — an Edm.Date Right: createdDateTime — an Edm.DateTimeOffset Right: recurringMeetingTime — an Edm.TimeOfDay Wrong: dueOn or startTime Right: instead both above are an Edm.DateTimeOffset | -| ✔ DO use the Duration type for durations, but if using an int, append the units. | Right: passwordValidityPeriod — an Edm.Duration Right: passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable) Wrong: passwordValidityPeriod — an Edm.Int32 | +|--------------------------------------------------------------------------------------|------------------------------------------------------------------------| +| :heavy_check_mark: **DO** use namespaces | Microsoft Graph model types can be declared within a [type namespaces](https://github.com/microsoft/api-guidelines/blob/graph/graph/type-namespaces) to reduce the need to prefix types with a qualifier to ensure uniqueness. | +| :heavy_check_mark: **DO** suffix date and time properties with | Right: dueDate — an Edm.Date Right: createdDateTime — an Edm.DateTimeOffset Right: recurringMeetingTime — an Edm.TimeOfDay Wrong: dueOn or startTime Right: instead both above are an Edm.DateTimeOffset | +| :heavy_check_mark: **DO** use the Duration type for durations, but if using an int, append the units. | Right: passwordValidityPeriod — an Edm.Duration Right: passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable) Wrong: passwordValidityPeriod — an Edm.Int32 | | ✖ DO NOT suffix property names with primitive type names unless the type is temporal. | Right: isEnabled or amount Wrong: enabledBool | -| ✔ DO prefix property names for properties concerning a different entity. | Right: siteWebUrl on driveItem, or userId on auditActor Wrong: webUrl on contact when its the companyWebUrl | -| ✔ DO prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | • Right: isEnabled or isResourceAccount • Wrong: enabled or allowResourcAccount • Right: allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below • Wrong: isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | +| :heavy_check_mark: **DO** prefix property names for properties concerning a different entity. | Right: siteWebUrl on driveItem, or userId on auditActor Wrong: webUrl on contact when its the companyWebUrl | +| :heavy_check_mark: **DO** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | • Right: isEnabled or isResourceAccount • Wrong: enabled or allowResourcAccount • Right: allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below • Wrong: isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | ### Uniform Resource Locators (URLs) @@ -180,30 +176,30 @@ navigation properties. We define a **top-level API category** as a coherent area of API functionality which covers one or multiple high-level use cases defined from customer and enterprise perspectives and represents one of the following: - +>> 1. A core *user-centric concept* of the Graph -- For example: /users, /groups or /me + - For example: /users, /groups or /me 1. A Microsoft *product or service offerings* covering multiple use cases -- For example: /teamwork, /directory + - For example: /teamwork, /directory 1. A *feature* offering covering a single use case and *shared* across multiple Microsoft products -- For example: /search, /notifications, /subscriptions, /files + - For example: /search, /notifications, /subscriptions, /files 1. *Administrative configuration* functions for specific products. (Note: this is not final and may be adjusted based on the survey results) -- For example: /admin/exchange + - For example: /admin/exchange 1. Internal Microsoft requirements for publishing Privileged and Hidden APIs, routing, and load testing -- For example: /loadTestEntities - + - For example: /loadTestEntities +>> Top-level API categories are aligned with documentation, developer tools, and in general are relatively stable. If a new category needs to be created, it should follow supporting governance @@ -215,12 +211,12 @@ OData specifications and [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). |Requirements| |----------------------------------------------------------------------------------------------------| -| ✔ DO support \$select, \$top, \$filter query options | -| ✔ DO support \$filter with eq, ne operations on properties of entities in the requested entity set | +| :heavy_check_mark: **DO** support \$select, \$top, \$filter query options | +| :heavy_check_mark: **DO** support \$filter with eq, ne operations on properties of entities in the requested entity set | | ✔ may support \$skip, \$count | -| ✔ DO use batch request to avoid too long query options | -| ✔ DO use request body with the content-type text/plain for POST queries | -| ✔ DO use request body with the content-type | +| :heavy_check_mark: **DO** use batch request to avoid too long query options | +| :heavy_check_mark: **DO** use request body with the content-type text/plain for POST queries | +| :heavy_check_mark: **DO** use request body with the content-type | Limitations of \$query requests made to Microsoft Graph: @@ -243,14 +239,13 @@ Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conv | Microsoft Graph rules for modeling resources| |-------------------------------------------------------------------------------------| -| ✔ DO verify that the primary id of an entity type is string | -| ✔ DO verify that the primary key must also be defined as a property. | -| ✔ DO verify that the primary key is composed of a single property and not multiple. | +| :heavy_check_mark: **DO** verify that the primary id of an entity type is string | +| :heavy_check_mark: **DO** verify that the primary key must also be defined as a property. | +| :heavy_check_mark: **DO** verify that the primary key is composed of a single property and not multiple. | | ✖ DO NOT add the property id to a complex type | -| **Serialization** | -| ✔ DO use an object as the root of all JSON payloads. | -| ✔ DO use a value property in the root object to return a collection. | -| ✔ DO include @odata.type annotations when the type is ambiguous. | +| :heavy_check_mark: **DO** use an object as the root of all JSON payloads. | +| :heavy_check_mark: **DO** use a value property in the root object to return a collection. | +| :heavy_check_mark: **DO** include @odata.type annotations when the type is ambiguous. | ### Recommended Modeling Patterns @@ -294,12 +289,12 @@ The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. - -| ✔ DO use POST to create new entities in insertable entity sets | +|Requirements|Severity| |------------------------------------------------------------------------------------| -| ✔ DO use PATCH to edit updatable resources | -| ✔ DO use DELETE to delete deletable resources | -| ✔ DO return a Location header with the edit URL or read URL of a created resource | +| :heavy_check_mark: **DO** use POST to create new entities in insertable entity sets | +| :heavy_check_mark: **DO** use PATCH to edit updatable resources | +| :heavy_check_mark: **DO** use DELETE to delete deletable resources | +| :heavy_check_mark: **DO** return a Location header with the edit URL or read URL of a created resource | For a complete list of standard REST operations you can refer to the [Microsoft REST API @@ -307,15 +302,17 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m #### Microsoft Graph rules for modeling behavior: -| ✔ DO use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | Error | -|--------------------------------------------------------------------------------------------|---------| -| ✔ DO use POST …/{collection} for creating resources. | Error | -| ✔ DO use PATCH …/{collection}/{id} for updating resources. | Error | -| ✖ AVOID using PUT …/{collection}/{id} for updating resources. | Warning | +|Requirements| +|--------------------------------------------------------------------------------------------| +| :heavy_check_mark: **DO** use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | Error | +| :heavy_check_mark: **DO** use POST …/{collection} for creating resources. | Error | +| :heavy_check_mark: **DO** use PATCH …/{collection}/{id} for updating resources. | Error | +| :no_entry: **AVOID** using PUT …/{collection}/{id} for updating resources. | Warning | | ✖ DO NOT use PATCH to replaces resources or PUT to partially update resources. | Error | -| ✖ AVOID patterns that require multiple round trips to complete a single logical action. | Warning | +| :no_entry: **AVOID** patterns that require multiple round trips to complete a single logical action. | Warning | | ✔ CONSIDER supporting return and omit-nulls preferences. | Warning | + ### Error Handling Microsoft REST API Guidelines provide guidelines that Microsoft REST APIs should @@ -325,41 +322,23 @@ undiscoverable and inconsistent error messages. You can improve API traceability and consistency by using recommended Graph error model: ``` { - "error": { - -"code": "BadRequest", - -"message": "Unsupported functionality", - -"target": "query", - -"details": [ - -{ - -"code": "301", - -"target": "\$search", - -"message": "\$search query option not supported" - -} - -], - -"innererror": { - -"code": "301", - -"message": "Cannot process the request because a required field is missing.", - -"stacktrace": [...], - -} - -} - + "code": "BadRequest", + "message": "Unsupported functionality", + "target": "query", + "details": [ + { + "code": "301", + "target": "\$search", + "message": "\$search query option not supported" + } + ], + "innererror": { + "code": "301", + "message": "Cannot process the request because a required field is missing.", + "stacktrace": [...], + } + } } ``` The following examples demonstrate error modeling for common use cases: @@ -369,18 +348,13 @@ The following examples demonstrate error modeling for common use cases: specify the top-level error code, message and target (optional). ``` { - -"error": { - -"code": "badRequest", - -"message": "Cannot process the request because it is malformed or incorrect.", - -"target": "Service X (Optional)" - -} - + "error": { + "code": "badRequest", + "message": "Cannot process the request because it is malformed or incorrect.", + "target": "Service X (Optional)" + } } +``` - **Detailed error**: A workload wants to report an error and provide service-specific details of the error via the innererror property of the @@ -389,63 +363,25 @@ The following examples demonstrate error modeling for common use cases: error code to help differentiate errors that share the same top-level error code but reported for different reasons. +``` { - -"error": { - -"code": "badRequest", - -"message": "Cannot process the request because it is malformed or incorrect.", - -"innererror": { - -"code": "requiredFieldOrParameterMissing", - -"message": "Cannot process the request because a required field is missing.", - -"stacktrace": "[StackTrace]" - -} - -} - -} - -- **Error with collection of related errors**: A workload wants to report an - error together with a collection of related errors via the details - collection property of the error object. - -{ - -"error": { - -"code": "forbidden", - -"message": "Access to the resource is restricted.", - -"details": [ - -{ - -"code": "unathorized", - -"message": "You are not authorized to access the resource" - -} - -] - -} - + "error": { + "code": "badRequest", + "message": "Cannot process the request because it is malformed or incorrect.", + "innererror": { + "code": "requiredFieldOrParameterMissing", + "message": "Cannot process the request because a required field or parameter is missing.", + "stacktrace": "[StackTrace]" + } + } } ``` -#### Microsoft Graph enforces the list of following error rules: - -| ✔ DO return an error property with a child code property in all error responses. | Error | +| Microsoft Graph enforces the following error rules|Severity| |---------------------------------------------------------------------------------------------|---------| -| ✔ DO return a 403 Forbidden error when insufficient scopes are present on the auth token. | Error | +| :heavy_check_mark: **DO** return an error property with a child code property in all error responses. | Error | +| :heavy_check_mark: **DO** return a 403 Forbidden error when insufficient scopes are present on the auth token. | Error | | ✔ CONSIDER returning a 404 Not found error if a 403 would result in information disclosure. | Warning | -| ✔ DO return a 429 Too many requests error when the caller has exceeded throttling limits. | Error | +| :heavy_check_mark: **DO** return a 429 Too many requests error when the caller has exceeded throttling limits. | Error | For a complete mapping of error codes to HTTP statuses please refer to the [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). @@ -463,7 +399,7 @@ breaking change. Additional Microsoft Graph rules most often observed in practice are summarized in the table below: -| ✔ DO use **not-breaking** changes:| +| :heavy_check_mark: **DO** use **not-breaking** changes:| |-----------------------------------| * Addition of an annotation OpenType="true" Addition of properties that are nullable or have a default value * Addition of a member to an evolvable enumeration 1. Removal, rename, or change to the type of an open extension @@ -544,37 +480,20 @@ navigation properties, functions, or actions. Example of property annotation: ``` -\ - -\ - -\ - -\ - -\ - -\ - -\ - -\ - -\ - -\ - -\ - -\ - -... - -\ + + + + + + + + + + + + + ... + ``` When the request URL contains a reference to a deprecated model element, the HTTP response includes a [Deprecation From e3ceb656d160f1a7caaa49b155daafa0a9ec86de Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 8 Nov 2021 17:00:11 -0500 Subject: [PATCH 187/729] Fixing legend --- graph/GuidelinesGraph.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index ab3f1cbb..b73ae2eb 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -54,9 +54,8 @@ a change or propose a new idea. This document offers prescriptive guidance labeled as follows: :heavy_check_mark: **DO** satisfy this specification if appropriate to your situation. No notification to the Graph API Review board is required. - -:no_entry: **AVOID** using this pattern. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. - +:no_entry: **DO NOT** use this pattern. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. +:ballot_box_with_check: **YOU SHOULD** fulfill this specification. If not following this advice, you MUST disclose your reason during the Graph API review. ## Design Approach @@ -113,9 +112,9 @@ appear in API URLs and payloads and should be descriptive and easy to understand. Therefore you should follow the rules in the table below: |Requirements|Example| |--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| -| :no_entry: **AVOID** redundant words in names. | Right: /places/{id}/**type** and /phones/{id}/**number** Wrong: /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | -| :no_entry: **AVOID** using brand names in type or property names. | Right: chat Wrong: teamsChat | -| :no_entry: **AVOID** using acronyms or abbreviations unless they are broadly understood. | Right: url or htmlSignature Wrong: msodsUrl or dlp | +| :no_entry: **DO NOT** use redundant words in names. | Right: /places/{id}/**type** and /phones/{id}/**number** Wrong: /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | +| :no_entry: **DO NOT** use brand names in type or property names. | Right: chat Wrong: teamsChat | +| :no_entry: **DO NOT** using acronyms or abbreviations unless they are broadly understood. | Right: url or htmlSignature Wrong: msodsUrl or dlp | | :heavy_check_mark: **DO** use singular nouns for type names. | Right: address Wrong: addresses | | :heavy_check_mark: **DO** use plural nouns for collections (for listing a type or collection properties). | Right: addresses Wrong: address | | :heavy_check_mark: **DO** pluralize the noun even when followed by an adjective (a "postpositive"). | Right: passersby or mothersInLaw Wrong: notaryPublics or motherInLaws | @@ -127,7 +126,7 @@ understand. Therefore you should follow the rules in the table below: | :heavy_check_mark: **DO** use lower camel case for *all* names and namespaces | Right: automaticRepliesStatus. Wrong: kebab-case or snake_case. | | :heavy_check_mark: **DO** case two-letter acronyms with the same case. | Right: ioLimit or totalIOAmount Wrong: iOLimit or totalIoAmount | | :heavy_check_mark: **DO** case three+ letter acronyms the same as a normal word. | Right: fidoKey or oauthUrl Wrong: webHTML | -| ✖ DO NOT capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | Right: subcategory, geocoordinate or crosswalk Wrong: metaData, semiCircle or airPlane | +|:no_entry: **DO NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | Right: subcategory, geocoordinate or crosswalk Wrong: metaData, semiCircle or airPlane | | :heavy_check_mark: **DO** capitalize within hyphenated and open (spaced) compound words. | Right: fiveYearOld, daughterInLaw or postOffice Wrong: paperclip, changingroom or fullmoon | #### Prefixes and Suffixes @@ -136,7 +135,7 @@ understand. Therefore you should follow the rules in the table below: | :heavy_check_mark: **DO** use namespaces | Microsoft Graph model types can be declared within a [type namespaces](https://github.com/microsoft/api-guidelines/blob/graph/graph/type-namespaces) to reduce the need to prefix types with a qualifier to ensure uniqueness. | | :heavy_check_mark: **DO** suffix date and time properties with | Right: dueDate — an Edm.Date Right: createdDateTime — an Edm.DateTimeOffset Right: recurringMeetingTime — an Edm.TimeOfDay Wrong: dueOn or startTime Right: instead both above are an Edm.DateTimeOffset | | :heavy_check_mark: **DO** use the Duration type for durations, but if using an int, append the units. | Right: passwordValidityPeriod — an Edm.Duration Right: passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable) Wrong: passwordValidityPeriod — an Edm.Int32 | -| ✖ DO NOT suffix property names with primitive type names unless the type is temporal. | Right: isEnabled or amount Wrong: enabledBool | +|:no_entry: **DO NOT** suffix property names with primitive type names unless the type is temporal. | Right: isEnabled or amount Wrong: enabledBool | | :heavy_check_mark: **DO** prefix property names for properties concerning a different entity. | Right: siteWebUrl on driveItem, or userId on auditActor Wrong: webUrl on contact when its the companyWebUrl | | :heavy_check_mark: **DO** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | • Right: isEnabled or isResourceAccount • Wrong: enabled or allowResourcAccount • Right: allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below • Wrong: isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | @@ -213,7 +212,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **DO** support \$select, \$top, \$filter query options | | :heavy_check_mark: **DO** support \$filter with eq, ne operations on properties of entities in the requested entity set | -| ✔ may support \$skip, \$count | +| :ballot_box_with_check: should \$skip, \$count | | :heavy_check_mark: **DO** use batch request to avoid too long query options | | :heavy_check_mark: **DO** use request body with the content-type text/plain for POST queries | | :heavy_check_mark: **DO** use request body with the content-type | @@ -242,7 +241,7 @@ Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conv | :heavy_check_mark: **DO** verify that the primary id of an entity type is string | | :heavy_check_mark: **DO** verify that the primary key must also be defined as a property. | | :heavy_check_mark: **DO** verify that the primary key is composed of a single property and not multiple. | -| ✖ DO NOT add the property id to a complex type | +| :no_entry: **DO NOT** add the property id to a complex type | | :heavy_check_mark: **DO** use an object as the root of all JSON payloads. | | :heavy_check_mark: **DO** use a value property in the root object to return a collection. | | :heavy_check_mark: **DO** include @odata.type annotations when the type is ambiguous. | @@ -307,10 +306,10 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m | :heavy_check_mark: **DO** use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | Error | | :heavy_check_mark: **DO** use POST …/{collection} for creating resources. | Error | | :heavy_check_mark: **DO** use PATCH …/{collection}/{id} for updating resources. | Error | -| :no_entry: **AVOID** using PUT …/{collection}/{id} for updating resources. | Warning | -| ✖ DO NOT use PATCH to replaces resources or PUT to partially update resources. | Error | -| :no_entry: **AVOID** patterns that require multiple round trips to complete a single logical action. | Warning | -| ✔ CONSIDER supporting return and omit-nulls preferences. | Warning | +| :no_entry: **DO NOT** use PUT …/{collection}/{id} for updating resources. | Warning | +| :no_entry: **DO NOT** use PATCH to replaces resources or PUT to partially update resources. | Error | +| :no_entry: **DO NOT** use patterns that require multiple round trips to complete a single logical action. | Warning | +| :ballot_box_with_check: CONSIDER supporting return and omit-nulls preferences. | Warning | ### Error Handling @@ -380,7 +379,7 @@ The following examples demonstrate error modeling for common use cases: |---------------------------------------------------------------------------------------------|---------| | :heavy_check_mark: **DO** return an error property with a child code property in all error responses. | Error | | :heavy_check_mark: **DO** return a 403 Forbidden error when insufficient scopes are present on the auth token. | Error | -| ✔ CONSIDER returning a 404 Not found error if a 403 would result in information disclosure. | Warning | +| :ballot_box_with_check: CONSIDER returning a 404 Not found error if a 403 would result in information disclosure. | Warning | | :heavy_check_mark: **DO** return a 429 Too many requests error when the caller has exceeded throttling limits. | Error | For a complete mapping of error codes to HTTP statuses please refer to the @@ -408,7 +407,7 @@ in the table below: * Changes to the length or format of opaque strings, such as resource IDs -| ✖ DO NOT use **breaking** changes | +|:no_entry: **DO NOT** use **breaking** changes | |-----------------------------------| * Changes to the URL or fundamental request/response associated with a resource * Changing semantics of resource representation From 088246febca35c19a07d0e5173a32da2ad84d03a Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 8 Nov 2021 17:58:45 -0500 Subject: [PATCH 188/729] Cleaning --- graph/GuidelinesGraph.md | 55 +++--- graph/Modelling with Subtypes Pattern.md | 24 +-- graph/ProtocolRuleset.md | 178 ------------------ ...{guidelines.md => SummaryOfGraphDeltas.md} | 0 4 files changed, 29 insertions(+), 228 deletions(-) delete mode 100644 graph/ProtocolRuleset.md rename graph/{guidelines.md => SummaryOfGraphDeltas.md} (100%) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index b73ae2eb..2f829564 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -14,9 +14,8 @@ Table of Contents ## Introduction -When building a digital ecosystem providing APIs that are easy to discover, -simple to use, fit to purpose, and consistent across your products can make the -difference between success and failure. +When building a digital ecosystem you should use API-first approach and start with design and development of your APIs. Considering API usability and creating APIs that are easy to discover, simple to use, fit to purpose, and consistent across your products will make the +difference between success and failure of your ecosystem. This document offers guidance that Graph API developer teams MUST follow to ensure that customers have a great experience. A new API design should meet the @@ -53,8 +52,10 @@ a change or propose a new idea. ### Legend This document offers prescriptive guidance labeled as follows: -:heavy_check_mark: **DO** satisfy this specification if appropriate to your situation. No notification to the Graph API Review board is required. -:no_entry: **DO NOT** use this pattern. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. +:heavy_check_mark: **DO** satisfy this specification. If not following this advice, you MUST disclose your reason during the Graph API review. + +:no_entry: **DO NOT** use this pattern. If not following this advice, you MUST disclose your reason during the Graph API review. + :ballot_box_with_check: **YOU SHOULD** fulfill this specification. If not following this advice, you MUST disclose your reason during the Graph API review. @@ -62,7 +63,7 @@ This document offers prescriptive guidance labeled as follows: The design of your API is arguably the most important investment you will make in it. The design of your API is what creates the first impression for -developers. Microsoft Graph APIs follow HTTP, REST, and JSON standards and +developers. Microsoft Graph APIs follow HTTP, REST, and JSON standards and are described using ODATA conventions and CSDL for schema definition (see [Documentation · OData - the Best Way to REST](https://www.odata.org/documentation/)). @@ -86,8 +87,8 @@ In general API design includes the following steps: - Specify errors -To create a good API you need to start with understanding your use cases and -supporting domain model. We describe domain models in terms of entities, their +To create a good API you need to start with understanding your **use cases** and +supporting domain model. We describe domain models in terms of entities or resources, their properties, and relationships and further refer to it as entity data model. There is no one-to-one correspondence between domain model elements and API resources as APIs usually support only customer-facing use cases. @@ -100,7 +101,7 @@ When resources are defined it’s time to think about the behavior of your API a define required operations and actions. At every step of your design you need to consider security, privacy and -compliance as an intrinsic parts of your API implementation. And finally based +compliance as an intrinsic components of your API implementation. And finally based on your API resources, their behavior, and anticipated exceptions you need to identify potential error scenarios with secure and descriptive messaging. @@ -114,7 +115,7 @@ understand. Therefore you should follow the rules in the table below: |--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| | :no_entry: **DO NOT** use redundant words in names. | Right: /places/{id}/**type** and /phones/{id}/**number** Wrong: /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | | :no_entry: **DO NOT** use brand names in type or property names. | Right: chat Wrong: teamsChat | -| :no_entry: **DO NOT** using acronyms or abbreviations unless they are broadly understood. | Right: url or htmlSignature Wrong: msodsUrl or dlp | +| :no_entry: **DO NOT** use acronyms or abbreviations unless they are broadly understood. | Right: url or htmlSignature Wrong: msodsUrl or dlp | | :heavy_check_mark: **DO** use singular nouns for type names. | Right: address Wrong: addresses | | :heavy_check_mark: **DO** use plural nouns for collections (for listing a type or collection properties). | Right: addresses Wrong: address | | :heavy_check_mark: **DO** pluralize the noun even when followed by an adjective (a "postpositive"). | Right: passersby or mothersInLaw Wrong: notaryPublics or motherInLaws | @@ -127,16 +128,16 @@ understand. Therefore you should follow the rules in the table below: | :heavy_check_mark: **DO** case two-letter acronyms with the same case. | Right: ioLimit or totalIOAmount Wrong: iOLimit or totalIoAmount | | :heavy_check_mark: **DO** case three+ letter acronyms the same as a normal word. | Right: fidoKey or oauthUrl Wrong: webHTML | |:no_entry: **DO NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | Right: subcategory, geocoordinate or crosswalk Wrong: metaData, semiCircle or airPlane | -| :heavy_check_mark: **DO** capitalize within hyphenated and open (spaced) compound words. | Right: fiveYearOld, daughterInLaw or postOffice Wrong: paperclip, changingroom or fullmoon | +| :heavy_check_mark: **DO** capitalize within hyphenated and open (spaced) compound words. | Right: fiveYearOld, daughterInLaw or postOffice Wrong: paperclip or fullmoon | #### Prefixes and Suffixes -|Requirements|Example| -|--------------------------------------------------------------------------------------|------------------------------------------------------------------------| -| :heavy_check_mark: **DO** use namespaces | Microsoft Graph model types can be declared within a [type namespaces](https://github.com/microsoft/api-guidelines/blob/graph/graph/type-namespaces) to reduce the need to prefix types with a qualifier to ensure uniqueness. | -| :heavy_check_mark: **DO** suffix date and time properties with | Right: dueDate — an Edm.Date Right: createdDateTime — an Edm.DateTimeOffset Right: recurringMeetingTime — an Edm.TimeOfDay Wrong: dueOn or startTime Right: instead both above are an Edm.DateTimeOffset | -| :heavy_check_mark: **DO** use the Duration type for durations, but if using an int, append the units. | Right: passwordValidityPeriod — an Edm.Duration Right: passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable) Wrong: passwordValidityPeriod — an Edm.Int32 | -|:no_entry: **DO NOT** suffix property names with primitive type names unless the type is temporal. | Right: isEnabled or amount Wrong: enabledBool | -| :heavy_check_mark: **DO** prefix property names for properties concerning a different entity. | Right: siteWebUrl on driveItem, or userId on auditActor Wrong: webUrl on contact when its the companyWebUrl | +|Requirements |Example| +|----------------------------------------------------------------------------|--------------------------------------------------| +| :heavy_check_mark: **DO** use namespaces | Microsoft Graph model types can be declared within a [type namespaces](./type-namespaces.md) to reduce the need to prefix types with a qualifier to ensure uniqueness. | +| :heavy_check_mark: **DO** suffix date and time properties with | Right: dueDate — an Edm.Date Right: createdDateTime — an Edm.DateTimeOffset Right: recurringMeetingTime — an Edm.TimeOfDay Wrong: dueOn or startTime Right: instead both above are an Edm.DateTimeOffset | +| :heavy_check_mark: **DO** use the Duration type for durations, but if using an int, append the units. | Right: passwordValidityPeriod — an Edm.Duration Right: passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable) Wrong: passwordValidityPeriod — an Edm.Int32| +|:no_entry: **DO NOT** suffix property names with primitive type names unless the type is temporal.| Right: isEnabled or amount Wrong: enabledBool| +| :heavy_check_mark: **DO** prefix property names for properties concerning a different entity.| Right: siteWebUrl on driveItem, or userId on auditActor Wrong: webUrl on contact when its the companyWebUrl| | :heavy_check_mark: **DO** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | • Right: isEnabled or isResourceAccount • Wrong: enabled or allowResourcAccount • Right: allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below • Wrong: isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | ### Uniform Resource Locators (URLs) @@ -309,7 +310,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m | :no_entry: **DO NOT** use PUT …/{collection}/{id} for updating resources. | Warning | | :no_entry: **DO NOT** use PATCH to replaces resources or PUT to partially update resources. | Error | | :no_entry: **DO NOT** use patterns that require multiple round trips to complete a single logical action. | Warning | -| :ballot_box_with_check: CONSIDER supporting return and omit-nulls preferences. | Warning | +| :ballot_box_with_check: **CONSIDER** supporting return and omit-nulls preferences. | Warning | ### Error Handling @@ -379,7 +380,7 @@ The following examples demonstrate error modeling for common use cases: |---------------------------------------------------------------------------------------------|---------| | :heavy_check_mark: **DO** return an error property with a child code property in all error responses. | Error | | :heavy_check_mark: **DO** return a 403 Forbidden error when insufficient scopes are present on the auth token. | Error | -| :ballot_box_with_check: CONSIDER returning a 404 Not found error if a 403 would result in information disclosure. | Warning | +| :ballot_box_with_check: **CONSIDER** returning a 404 Not found error if a 403 would result in information disclosure. | Warning | | :heavy_check_mark: **DO** return a 429 Too many requests error when the caller has exceeded throttling limits. | Error | For a complete mapping of error codes to HTTP statuses please refer to the @@ -398,8 +399,8 @@ breaking change. Additional Microsoft Graph rules most often observed in practice are summarized in the table below: -| :heavy_check_mark: **DO** use **not-breaking** changes:| - |-----------------------------------| +:heavy_check_mark: **DO use not-breaking changes** + ----------------------------------- * Addition of an annotation OpenType="true" Addition of properties that are nullable or have a default value * Addition of a member to an evolvable enumeration 1. Removal, rename, or change to the type of an open extension * Removal, rename, or change to the type of an annotation *Introduction of paging to existing collections @@ -407,8 +408,8 @@ in the table below: * Changes to the length or format of opaque strings, such as resource IDs -|:no_entry: **DO NOT** use **breaking** changes | -|-----------------------------------| +:no_entry: **DO NOT use breaking changes** +----------------------------------- * Changes to the URL or fundamental request/response associated with a resource * Changing semantics of resource representation * Removal, rename, or change to the type of a declared property @@ -417,7 +418,7 @@ in the table below: * Addition of a Nullable="false" properties to existing types * Addition of a Nullable="false" parameters to existing actions and functions * Adding attributes to existing nodes is considered breaking. -* Adding annotations ags:IsHidden="true". | + For the full list of rules you can refer to [this section of the OData V4 spec](https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209). @@ -543,11 +544,11 @@ You can find references in the most common patterns in the table below: | Pattern | Description | Reference | |-------------------------|-------------|----------------------------------------------------------------------------------------------------------| -| Key Property | | [Key Property](https://dev.azure.com/msazure/One/_wiki/wikis/Microsoft%20Graph%20Partners/103125/Design) | +| Key Property | | [Key Property](./evolvable-enums.md) | | Entity Type | | | | Complex Type | | | | Shared Type | | | -| Type Hierarchy | | | +| Type Hierarchy | | [Modeling with Subtypes](./Modelling%20with%20Subtypes%20Pattern.md) | | Dictionary | | | | Evolvable Enums | | | | Type Namespace | | | diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index 8535f8ce..136f0f1e 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -115,39 +115,17 @@ GET returns a collection where each element can be a user or a service principal, and has an additional property @odata.type to show subtype for each variant: -```js +``` { -     "@odata.context": "https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", -     "value": [ -         { -             "@**odata.type**": "\#**microsoft.graph.user**", -             "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", -             "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" -             // ... -         } -     ] - } ``` -### Prescriptive Guidance -This document offers prescriptive guidance labeled as follows: - -:white_check_mark: **DO** fulfill this specification. If you feel you need an exception, contact the Graph API Review board **prior** to implementation. - -:ballot_box_with_check: **YOU SHOULD** fulfill this specification. If not following this advice, you MUST disclose your reason during the Graph API review. - -:heavy_check_mark: **YOU MAY** satisfy this specification if appropriate to your situation. No notification to the Graph API Review board is required. - -:warning: **YOU SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Graph API Review board review. - -:no_entry: **DO NOT** adopt this pattern. If you feel you need an exception, contact the Graph API Review board **prior** to implementation.  diff --git a/graph/ProtocolRuleset.md b/graph/ProtocolRuleset.md deleted file mode 100644 index 72a8c3c2..00000000 --- a/graph/ProtocolRuleset.md +++ /dev/null @@ -1,178 +0,0 @@ -[[_TOC_]] - -API owners that have onboarded to Microsoft Graph: - -- [x] Preparing for the API review process -- [x] Designing new APIs or updating existing ones - -and are looking to do one or more of the following: - -- [x] Understand the requirements of Microsoft Graph APIs -- [ ] Address issues raised by the schema validation CI pipeline -- [ ] Fix existing issues because grace period is expiring - -# Schema validation - -Currently schema validation is run by [Graph-Studio](Update-schema/Graph-Studio) on the build pipeline when workloads push to their test branches or create a pull request to master. -Workloads should have the prerogative to address all the errors raised by the validation and ensure that their schema is as compliant as possible -to the rules defined before publishing their changes. - -There is however the ability to suppress **noncritical errors** for some time, to allow workloads to -plan and address errors that cannot be immediately resolved. Please see [Tracing-and-suppressions](Update-schema/Graph-Studio/Tracing-and-suppressions). - -Error messages can have the following severity levels: - -| Severity | Description | -| :---------- | :----------------------------------------------------------------------------------------------------- | -| Critical | Must be fixed before publishing. Cannot be suppressed. This Error is likely to break AGS if published. | -| Error | Can be suppressed during publishing. This is a hard error that should be fixed before moving to v1.0. | -| Warning | Can be suppressed during publishing. This is a suggestion so as to conform to our coding style. | -| Information | No need to suppress. It will not block publishing. This is for information purposes only. | - -# OData validation - -Graph Studio performs the full suite of OData validations. Because Microsoft Graph rejects any schema containing OData violations, these violations are treated as `Critical` errors and cannot be suppressed. OData violation error codes are prefixed with `Schema.OData.{EdmErrorCode}`. - -Example OData infraction: - -```log -2020-11-30 21:47:51Z Critical Schema.OData.InvalidName: /Schemas/beta-Prod.csdl: [env=Prod;version=beta] '/ComplexType[testType]/Property[invalidProperty ]' The specified name is not allowed: 'invalidProperty '. -``` - -For a complete list of the OData error codes please see the [OData Validation Documentation](https://docs.microsoft.com/en-us/dotnet/api/microsoft.odata.edm.validation.edmerrorcode). - -# Breaking change analysis - -Breaking change analysis is performed by comparing latest (master) schema with current local schema. Each entry provides the file where the breaking change has occurred, and the element which triggered the error. The errors fall into one of three buckets: - -- `Schema.BreakingChange.CannotAdd` -- `Schema.BreakingChange.CannotChange` -- `Schema.BreakingChange.CannotDelete` - -Making changes to AGS annotations is not considered breaking, except adding `ags:IsHidden="true"` which makes an already public API private. Making changes to existing elements, or removing existing elements is considered breaking. Adding new elements is allowed and not considered breaking, but there are exceptions: - -- Adding `EnumType` members for non-extensible enumerations is considered breaking. -- Adding `Nullable="false"` properties to existing types is considered breaking. -- Adding `Nullable="false"` parameters to existing actions and functions is considered breaking. -- Adding attributes to existing nodes is considered breaking. - - Adding AGS annotations is exempted, except `ags:IsHidden="true"`. - - Adding `OpenType="true"` is exempted. - -# Validation for Private Preview API changes - -Private preview API validation is performed by comparing latest (master) schema with current local schema. Each entry provides the file where the validation has occurred, and the element which triggered the error. The errors fall into one of the following buckets: - -- `PrivatePreview.IsNotHidden` -- `PrivatePreview.Deprecated` -- `PrivatePreview.DeprecationDate` -- `PrivatePreview.RemovalDate` - -The following are the rules that private preview API changes must follow: - -- All elements added must be marked as hidden. -- All elements added must be deprecated. -- Deprecation date must be earlier than the current date. -- Removal date must not be later than 90 days from deprecation date. - -# JSON Description Validation for Public Schema Changes - -Pull requests with public schema changes need to have the below duly filled Json template as part of the pull request description: Please see [Pull-request-json-description](Update-schema/Pull-request-json-description). - -If `IsAPIForPrivatePreview` is set to "Yes", then all changes need to have `ags:IsHidden="true"` and not appear in the final public metadata. -In this case, `ChangelogPullRequestUrl` and `DocumentationPullRequestUrl` can be left blank. -No change will go in without having `IsPrivacyReviewCompleted` set as yes and a valid `PrivacyReviewUrl`. Please see [Privacy-review](Privacy-review). - -Errors from validation of the pull request description fall into one of the following buckets: - -| Code | Severity | Description | -| :---------------------------------------------------- | :--------- | :------------------------------------------------------------------------------------ | -| `Schema.PullRequest.InvalidDescriptionJson` | `Critical` | The pull request description Json is missing or has some formatting issues. | -| `Schema.PullRequest.ValueMissingError` | `Critical` | A required value is missing from the description Json. | -| `Schema.PullRequest.KeyMissingError` | `Critical` | A required key is missing from the description Json. | -| `Schema.PullRequest.PrivacyReviewNotCompleted` | `Critical` | The IsPrivacyReviewCompleted value must be set to True / Yes. | -| `Schema.PullRequest.InvalidPrivacyReviewUrl` | `Critical` | The PrivacyReviewUrl is not a valid privacy review URL. | -| `Schema.PullRequest.InvalidGithubUrl` | `Critical` | The DocumentationPullRequestUrl / ChangelogPullRequestUrl must be a valid Github URL. | -| `Schema.PullRequest.InvalidAPIReviewUrl` | `Critical` | The APIReviewApprovalPullRequestUrl is not a valid API review URL. | -| `Schema.PullRequest.PullRequestFetchError` | `Critical` | There was an error fetching the API review pull request. | -| `Schema.PullRequest.APIReviewPullRequestNotCompleted` | `Critical` | The API review pull request is not complete. | - -# Microsoft Graph ruleset - -Beyond OData and Breaking Change analysis, Graph Studio performs its own set of schema validations. These are typically best practices and naming conventions, but could also find semantic issues with the schemas. Before a schema can be published we must validate that it can be loaded by Microsoft Graph, which has its own set of criteria as to what makes a schema valid across all workloads. Because Microsoft Graph will reject any schemas not conforming to its criteria, any infractions are handled as `Critical` errors and cannot be suppressed. - -| Code | Severity | Description | -| :------------------------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Naming Validation** | | | -| `Schema.Validation.CamelCase` | `Error` | Names must be in lower camel case. | -| `Schema.Validation.NamespaceCamelCase` | `Error` | Namespaces must be in lower camel case. | -| `Schema.Validation.UseEmail` | `Warning` | A property name should use `email` instead of `mail`. | -| `Schema.Validation.SuffixTime` | `Error` | If a property has the type `Edm.Time`, its name must end in `Time` e.g. `startTime`. | -| `Schema.Validation.SuffixDate` | `Error` | If a property has the type `Edm.Date`, its name must end in `Date` (e.g. `birthDate`) or `MonthYear` (e.g. `startMonthYear`). | -| `Schema.Validation.SuffixDateTime` | `Error` | If a property has the type `DateTimeOffset`, its name must end in `DateTime` (e.g. `receivedDateTime`). | -| *`Schema.Validation.Case2LetterAcronyms` | `Error` | 2 letter acronyms should be cased with the same case (e.g. `prDescription`, `availableOnPC`). | -| *`Schema.Validation.Case3PlusLetterAcronyms` | `Error` | 3+ letter acronyms should be cased the same way as regular words (e.g. `adoPipeline`, `advancedRpc`). | -| **Primary key validation** | | | -| `Schema.Validation.EntityKeyMustBeString` | `Error` | Check to verify that the primary id of an entity type is `string`. | -| `Schema.Validation.PrimaryKeyMustBeDefinedAsProperty` | `Error` | Check to verify that the primary key must also be defined as a property. | -| `Schema.Validation.PrimaryKeyMustNotBeComposite` | `Error` | Check to verify that the primary key is composed of a single property and not multiple. | -| `Schema.Validation.AvoidComplexTypeId` | `Error` | A complex type must not have the property `id`. | -| **Property name validation** | | | -| `Schema.Validation.PropertyMustNotBeNamedType` | `Error` | A property name should not be `"type"`. | -| `Schema.Validation.PropertyNamesShouldNotStartWithTypeName` | `Error` | Property names should not start with type name. | -| `Schema.Validation.PropertyNameMustNotEndInPrimitiveType` | `Error` | Property names must not end in primitive types unless the type is temporal. | -| `Schema.Validation.SingularNoun` | `Warning` | Non-collection property names should be singular. | -| `Schema.Validation.PluralNoun` | `Warning` | Collection property names should be plural. | -| `Schema.Validation.EntityTypeNameShouldBeSingular` | `Warning` | Entity type name should be singular. | -| **Enum validation** | | | -| `Schema.Validation.EnumShouldBeEvolvable` | `Warning` | Enums should be evolvable. | -| `Schema.Validation.EnumMemberValuesShouldBeZeroOrPowersOfTwo` | `Warning` | Consider using zero or powers of two for flag enum member values. | -| **Stream validation** | | | -| `Schema.Validation.MediaEntityTypesCannotContainSubstreams` | `Warning` | Streams must not define a property of type `Edm.Stream`. | -| `Schema.Validation.MediaEntityTypesCannotInheritFromABaseType` | `Warning` | Streams cannot inherit from a base type. | -| **Structure validation** | | | -| `Schema.Validation.OperationsMustBeBound` | `Error` | Actions and Functions must have an IsBound='true' attribute and the first parameter must be the binding parameter. This is an AGS limitation. | -| `Schema.Validation.NavigationPropertyBindingMissing` | `Warning` | `NavigationProperty` without `ContainsTarget` must define `NavigationPropertyBinding` in Singleton/EntitySet. | -| `Schema.Validation.OperationsShouldBeAvoided` | `Warning` | Operations with names containing add, create, update, delete or remove should be avoided whenever possible. | -| *`Schema.Validation.ParallelCollections` | `Error` | Do not use parallel collections; use collections of complex types instead. | -| *`Schema.Validation.ProperCollections` | `Warning` | Consider using a proper collection rather than `property1`, `property2`, etc. | -| *`Schema.Validation.EntitySetNavigationProperties` | `Error` | Entity sets should have valid navigation properties. | -| *`Schema.Validation.SingletonNavigationProperties` | `Error` | Singletons should have valid navigation properties. | -| **Cross schema validation** | | | -| `Schema.Validation.EntityWithoutMaster` | `Critical` | Ensure that all entities have the ags:IsMaster="true" or ags:IsShared="true" annotation. | -| `Schema.Validation.TypeOverridesBaseProperty` | `Critical` | Types cannot override their base properties. | -| `Schema.Validation.PropertyAlreadyExists` | `Critical` | Entity type cannot redefine properties already defined by another workload. | -| `Schema.Validation.InconsistentSharedType` | `Critical` | Ensure that shared type definitions are consistent across workloads.
    If multiple workloads define a shared type, they must have the exact same definition of that type. | -| `Schema.Validation.ElementAlreadyExists` | `Critical` | Different elements cannot share the same name.
    If you have an entity called `foo`, you cannot have an action `foo` in the same namespace. | -| `Schema.Validation.NavigationPropertyContainsForeignTarget` | `Error` | Navigation property cannot contain target from a different workload. | - -\*Validation has not yet been automated in Graph Studio. However, workloads should adhere to these specifications so as not to risk having their schemas broken in the future. - -# Microsoft Graph protocol ruleset (Not implemented yet) - -| Name | Severity | -| ----------------------------------------------------------------------------------------------- | -------- | -| **Request patterns** | | -| ✔ DO use `GET …/{collection}` and `GET …/{collection}/{id}` for listing and reading resources. | Error | -| ✔ DO use `POST …/{collection}` for creating resources. | Error | -| ✔ DO use `PATCH …/{collection}/{id}` for updating resources. | Error | -| ✖ AVOID using `PUT …/{collection}/{id}` for updating resources. | Warning | -| ✖ DO NOT use `PATCH` to replaces resources or `PUT` to partially update resources. | Error | -| ✖ AVOID patterns that require multiple round trips to complete a single logical action. | Warning | -| ✔ CONSIDER supporting `return`, and `omit-nulls` preferences. | Warning | -| **Serialization** | | -| ✔ DO use an object as the root of all JSON payloads. | Error | -| ✔ DO use a `value` property in the root object to return a collection. | Error | -| ✔ DO include `@odata.type` annotations when the type is ambiguous. | Warning | -| **Authorization** | | -| ✖ DO NOT use a scope ending with `.Read` to authorize a data modification. | Error | -| **Errors** | | -| ✔ DO return an `error` property with a child `code` property in all error responses. | Error | -| ✔ DO return a `403 Forbidden` error when insufficient scopes are present on the auth token. | Error | -| ✔ CONSIDER returning a `404 Not found` error if a `403` would result in information disclosure. | Warning | -| ✔ DO return a `429 Too many requests` error when the caller has exceeded throttling limits. | Error | - -# Contacts - -| Area | Contact | -| :------ | :------------------------------------------------------------------------------------------------ | -| Support | [StackOverflow](https://stackoverflow.microsoft.com/questions/tagged/1096) tag `[MicrosoftGraph]` | diff --git a/graph/guidelines.md b/graph/SummaryOfGraphDeltas.md similarity index 100% rename from graph/guidelines.md rename to graph/SummaryOfGraphDeltas.md From 58ead393a9c52a50da229b66d8119c07fd036f56 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 8 Nov 2021 19:17:27 -0500 Subject: [PATCH 189/729] Combine behavior rules --- graph/GuidelinesGraph.md | 61 ++++------- graph/Modelling with Facets Pattern.md | 131 +++++++++++++++++++++++ graph/Modelling with Flat Bag Pattern.md | 131 +++++++++++++++++++++++ 3 files changed, 281 insertions(+), 42 deletions(-) create mode 100644 graph/Modelling with Facets Pattern.md create mode 100644 graph/Modelling with Flat Bag Pattern.md diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 2f829564..47bd64e3 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -131,12 +131,12 @@ understand. Therefore you should follow the rules in the table below: | :heavy_check_mark: **DO** capitalize within hyphenated and open (spaced) compound words. | Right: fiveYearOld, daughterInLaw or postOffice Wrong: paperclip or fullmoon | #### Prefixes and Suffixes -|Requirements |Example| -|----------------------------------------------------------------------------|--------------------------------------------------| +|Requirements |Example| +|----------------------------------------------------------|------------------------------------------------| | :heavy_check_mark: **DO** use namespaces | Microsoft Graph model types can be declared within a [type namespaces](./type-namespaces.md) to reduce the need to prefix types with a qualifier to ensure uniqueness. | | :heavy_check_mark: **DO** suffix date and time properties with | Right: dueDate — an Edm.Date Right: createdDateTime — an Edm.DateTimeOffset Right: recurringMeetingTime — an Edm.TimeOfDay Wrong: dueOn or startTime Right: instead both above are an Edm.DateTimeOffset | | :heavy_check_mark: **DO** use the Duration type for durations, but if using an int, append the units. | Right: passwordValidityPeriod — an Edm.Duration Right: passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable) Wrong: passwordValidityPeriod — an Edm.Int32| -|:no_entry: **DO NOT** suffix property names with primitive type names unless the type is temporal.| Right: isEnabled or amount Wrong: enabledBool| +|:no_entry: **DO NOT** use suffix property names with primitive type names unless the type is temporal.| Right: isEnabled or amount Wrong: enabledBool| | :heavy_check_mark: **DO** prefix property names for properties concerning a different entity.| Right: siteWebUrl on driveItem, or userId on auditActor Wrong: webUrl on contact when its the companyWebUrl| | :heavy_check_mark: **DO** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | • Right: isEnabled or isResourceAccount • Wrong: enabled or allowResourcAccount • Right: allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below • Wrong: isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | @@ -178,31 +178,21 @@ which covers one or multiple high-level use cases defined from customer and enterprise perspectives and represents one of the following: >> 1. A core *user-centric concept* of the Graph - - For example: /users, /groups or /me - 1. A Microsoft *product or service offerings* covering multiple use cases - - For example: /teamwork, /directory - 1. A *feature* offering covering a single use case and *shared* across multiple Microsoft products - - For example: /search, /notifications, /subscriptions, /files - 1. *Administrative configuration* functions for specific products. (Note: this is not final and may be adjusted based on the survey results) - - For example: /admin/exchange - 1. Internal Microsoft requirements for publishing Privileged and Hidden APIs, routing, and load testing - - For example: /loadTestEntities >> Top-level API categories are aligned with documentation, developer tools, and in -general are relatively stable. If a new category needs to be created, it should -follow supporting governance +general are relatively stable. If a new category needs to be created, it requires an API REview and an API Council approval. #### Query @@ -213,7 +203,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **DO** support \$select, \$top, \$filter query options | | :heavy_check_mark: **DO** support \$filter with eq, ne operations on properties of entities in the requested entity set | -| :ballot_box_with_check: should \$skip, \$count | +| :ballot_box_with_check: **CONSIDER** supporting \$skip, \$count | | :heavy_check_mark: **DO** use batch request to avoid too long query options | | :heavy_check_mark: **DO** use request body with the content-type text/plain for POST queries | | :heavy_check_mark: **DO** use request body with the content-type | @@ -237,7 +227,7 @@ query options part of the URL in the request body as described in the chapter [OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). -| Microsoft Graph rules for modeling resources| +| Additional Microsoft Graph rules for modeling resources| |-------------------------------------------------------------------------------------| | :heavy_check_mark: **DO** verify that the primary id of an entity type is string | | :heavy_check_mark: **DO** verify that the primary key must also be defined as a property. | @@ -255,22 +245,18 @@ of properties are three most often used patterns in Microsoft Graph today: - Type hierarchy is represented by one abstract base type with a few common properties and one sub-type for each variant - [api-guidelines/adding-subtypes.md at graph · microsoft/api-guidelines - (github.com)](https://github.com/microsoft/api-guidelines/blob/graph/graph/adding-subtypes.md) + [Modelling with Subtypes Pattern](./Modelling%20with%20Subtypes%20Pattern.md) - Facets are represented by a single entity type with common properties and one facet property (of complex type) per variant. The facet properties only have a value when the object represents that variant - [api-guidelines/adding-subtypes.md at graph · microsoft/api-guidelines - (github.com)](https://github.com/microsoft/api-guidelines/blob/graph/graph/adding-subtypes.md) + [Modelling with Facets Pattern](./Modelling%20with%20Facets%20Pattern.md) - Flat bag of properties is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required/meaningful for the variant given - by the type property. [api-guidelines/adding-subtypes.md at graph · - microsoft/api-guidelines - (github.com)](https://github.com/microsoft/api-guidelines/blob/graph/graph/adding-subtypes.md) + by the type property. [Modelling with Flat Bag Pattern](./Modelling%20with%20Flat%20Bag%20Pattern.md) The following table describes shows summary of main qualities for each pattern and will help to select a pattern preferred for your use case. @@ -289,21 +275,15 @@ The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. -|Requirements|Severity| -|------------------------------------------------------------------------------------| -| :heavy_check_mark: **DO** use POST to create new entities in insertable entity sets | -| :heavy_check_mark: **DO** use PATCH to edit updatable resources | -| :heavy_check_mark: **DO** use DELETE to delete deletable resources | -| :heavy_check_mark: **DO** return a Location header with the edit URL or read URL of a created resource | - -For a complete list of standard REST operations you can refer to the [Microsoft -REST API -Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). #### Microsoft Graph rules for modeling behavior: -|Requirements| +|Requirements|Severity| |--------------------------------------------------------------------------------------------| +| :heavy_check_mark: **DO** use POST to create new entities in insertable entity sets | Error | +| :heavy_check_mark: **DO** use PATCH to edit updatable resources | Error | +| :heavy_check_mark: **DO** use DELETE to delete deletable resources | Error | +| :heavy_check_mark: **DO** return a Location header with the edit URL or read URL of a created resource | Error | | :heavy_check_mark: **DO** use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | Error | | :heavy_check_mark: **DO** use POST …/{collection} for creating resources. | Error | | :heavy_check_mark: **DO** use PATCH …/{collection}/{id} for updating resources. | Error | @@ -312,6 +292,9 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m | :no_entry: **DO NOT** use patterns that require multiple round trips to complete a single logical action. | Warning | | :ballot_box_with_check: **CONSIDER** supporting return and omit-nulls preferences. | Warning | +For a complete list of standard REST operations you can refer to the [Microsoft +REST API +Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). ### Error Handling @@ -396,20 +379,14 @@ In general, making changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a breaking change. -Additional Microsoft Graph rules most often observed in practice are summarized -in the table below: - -:heavy_check_mark: **DO use not-breaking changes** - ----------------------------------- +:heavy_check_mark: **DO use not-breaking changes:** * Addition of an annotation OpenType="true" Addition of properties that are nullable or have a default value * Addition of a member to an evolvable enumeration 1. Removal, rename, or change to the type of an open extension * Removal, rename, or change to the type of an annotation *Introduction of paging to existing collections * Changes to error codes Changes to the order of properties * Changes to the length or format of opaque strings, such as resource IDs - -:no_entry: **DO NOT use breaking changes** ------------------------------------ +:no_entry: **DO NOT use breaking changes:** * Changes to the URL or fundamental request/response associated with a resource * Changing semantics of resource representation * Removal, rename, or change to the type of a declared property diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md new file mode 100644 index 00000000..136f0f1e --- /dev/null +++ b/graph/Modelling with Facets Pattern.md @@ -0,0 +1,131 @@ +# Type Hierarchy + +Microsoft Graph API Design Pattern + +  + +*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +## Context + +Let’s assume you need to model an API to manage groups in an +organization, where employees can create groups and become owners of the +group by default. At the same time to support business processes some +groups may be created automatically by daemon applications using a +service principal account. In this case the service principle will +become the group owner. People and service principles have some common +and some unique properties such as both have unique identifiers and +credentials, but users will have additional properties such as email and +manager for example. Conversely a service principle won’t have a manager +assigned but may have an associated application identifier and a +description. + +Additional requirement for the API model is to be flexible and +accommodate future needs like a device may become the owner of a group. + +## Problem + +How to model group owners to allow a heterogeneous collection of users +and principles so that API design can reflect common and relevant +properties for each subset of owners and accommodate new owner types in +future? + +## Solution +-------- + +OData allows us to design collections of entities (entity sets, multi +valued navigation properties) with values of different types using +**type hierarchy**, where there is one abstract base type with a few +common properties and one sub-type for each variant of the entity. In +the current version of Microsoft Graph there are many collections of +items that represent slightly different things, variants of one concept. + +## Issues and Considerations +------------------------- + +When introducing a new subtype, you need to ensure that the new subtype +doesn't change the semantic of the type hierarchy with it's implicit +constraints. + +There are a **few potential risks** for client applications when new +sub-types are introduced: + +- De-serialization code might break because of missing +properties in returned collection items. Even though property X was +mandatory on all subtypes previously returned, the new subtype might not +have this property and the client code needs to deal with that. + +- Client libraries for strongly typed language might ignore some +of the values in the @odata.type property without further configuration +and need to be configured to be able to pick the right (client) type to +deserialize into. + +To minimize impact on clients type hierarchy can be refined by +annotating the collections with OData derived type constraints (see +validation vocabulary). This annotation restricts the values to certain +sub-trees of an inheritance hierarchy. It makes it very explicit that +the collection only contains elements of some of the subtypes and helps +to not return objects of a type that is semantically not suitable. In +addition, you can follow some of the mitigation techniques such as: + +- Avoid overgeneralized base types + +- Think about roll-out sequence + - Consider that Microsoft Graph does not return objects from a workload +that has a type that is not configured in current metadata. To avoid +inconsistencies, follow a two-step process: + - Introduce the entity type to the Graph metadata but don’t +return objects of the type in any of the heterogeneous collections. + - Enable your workload to return objects of the new type as items +of collection. + + +- Allow time for testing + - Inform the clients about the change and allow them to test the +changes in beta. Time is required to implement the code necessary to +deal with the new entity type, both in terms of de-serialization as well +as integrating it into the rest of the application. + +- Communicate the change in semantics + + - It is necessary for the client developers to incorporate the new +semantic into their application/service, even if the change is perceived +to be small. This requires early communication and clear documentation +of what the new type represents and why/how it is considered a subtype +of the original abstract type of the collection. + +## When to Use this Pattern +------------------------ + +The Type hierarchy pattern is well familiar to OOP developers and well +suited for strongly typed client programming languages. + +There are related patterns to consider such as +[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) +and [Flat bag of +properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). + +## Example +------- + +GET +[https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners](https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners)  +returns a collection where each element can be a user or a service +principal, and has an additional property @odata.type to show subtype +for each variant: +``` +{ +    "@odata.context": +"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", +    "value": [ +        { +            "@**odata.type**": "\#**microsoft.graph.user**", +            "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", +            "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" +            // ... +        } +    ] +} +``` diff --git a/graph/Modelling with Flat Bag Pattern.md b/graph/Modelling with Flat Bag Pattern.md new file mode 100644 index 00000000..136f0f1e --- /dev/null +++ b/graph/Modelling with Flat Bag Pattern.md @@ -0,0 +1,131 @@ +# Type Hierarchy + +Microsoft Graph API Design Pattern + +  + +*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +## Context + +Let’s assume you need to model an API to manage groups in an +organization, where employees can create groups and become owners of the +group by default. At the same time to support business processes some +groups may be created automatically by daemon applications using a +service principal account. In this case the service principle will +become the group owner. People and service principles have some common +and some unique properties such as both have unique identifiers and +credentials, but users will have additional properties such as email and +manager for example. Conversely a service principle won’t have a manager +assigned but may have an associated application identifier and a +description. + +Additional requirement for the API model is to be flexible and +accommodate future needs like a device may become the owner of a group. + +## Problem + +How to model group owners to allow a heterogeneous collection of users +and principles so that API design can reflect common and relevant +properties for each subset of owners and accommodate new owner types in +future? + +## Solution +-------- + +OData allows us to design collections of entities (entity sets, multi +valued navigation properties) with values of different types using +**type hierarchy**, where there is one abstract base type with a few +common properties and one sub-type for each variant of the entity. In +the current version of Microsoft Graph there are many collections of +items that represent slightly different things, variants of one concept. + +## Issues and Considerations +------------------------- + +When introducing a new subtype, you need to ensure that the new subtype +doesn't change the semantic of the type hierarchy with it's implicit +constraints. + +There are a **few potential risks** for client applications when new +sub-types are introduced: + +- De-serialization code might break because of missing +properties in returned collection items. Even though property X was +mandatory on all subtypes previously returned, the new subtype might not +have this property and the client code needs to deal with that. + +- Client libraries for strongly typed language might ignore some +of the values in the @odata.type property without further configuration +and need to be configured to be able to pick the right (client) type to +deserialize into. + +To minimize impact on clients type hierarchy can be refined by +annotating the collections with OData derived type constraints (see +validation vocabulary). This annotation restricts the values to certain +sub-trees of an inheritance hierarchy. It makes it very explicit that +the collection only contains elements of some of the subtypes and helps +to not return objects of a type that is semantically not suitable. In +addition, you can follow some of the mitigation techniques such as: + +- Avoid overgeneralized base types + +- Think about roll-out sequence + - Consider that Microsoft Graph does not return objects from a workload +that has a type that is not configured in current metadata. To avoid +inconsistencies, follow a two-step process: + - Introduce the entity type to the Graph metadata but don’t +return objects of the type in any of the heterogeneous collections. + - Enable your workload to return objects of the new type as items +of collection. + + +- Allow time for testing + - Inform the clients about the change and allow them to test the +changes in beta. Time is required to implement the code necessary to +deal with the new entity type, both in terms of de-serialization as well +as integrating it into the rest of the application. + +- Communicate the change in semantics + + - It is necessary for the client developers to incorporate the new +semantic into their application/service, even if the change is perceived +to be small. This requires early communication and clear documentation +of what the new type represents and why/how it is considered a subtype +of the original abstract type of the collection. + +## When to Use this Pattern +------------------------ + +The Type hierarchy pattern is well familiar to OOP developers and well +suited for strongly typed client programming languages. + +There are related patterns to consider such as +[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) +and [Flat bag of +properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). + +## Example +------- + +GET +[https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners](https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners)  +returns a collection where each element can be a user or a service +principal, and has an additional property @odata.type to show subtype +for each variant: +``` +{ +    "@odata.context": +"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", +    "value": [ +        { +            "@**odata.type**": "\#**microsoft.graph.user**", +            "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", +            "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" +            // ... +        } +    ] +} +``` From 51059ed395e90572426671e4fc051a0edba3da07 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 8 Nov 2021 19:19:37 -0500 Subject: [PATCH 190/729] formatting --- graph/GuidelinesGraph.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 47bd64e3..cab6d373 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -278,8 +278,8 @@ response pattern should be one of the first implementation decisions you make. #### Microsoft Graph rules for modeling behavior: -|Requirements|Severity| -|--------------------------------------------------------------------------------------------| +|Requirements |Severity| +|----------------------------------------------|----------------------------------------------| | :heavy_check_mark: **DO** use POST to create new entities in insertable entity sets | Error | | :heavy_check_mark: **DO** use PATCH to edit updatable resources | Error | | :heavy_check_mark: **DO** use DELETE to delete deletable resources | Error | @@ -290,7 +290,7 @@ response pattern should be one of the first implementation decisions you make. | :no_entry: **DO NOT** use PUT …/{collection}/{id} for updating resources. | Warning | | :no_entry: **DO NOT** use PATCH to replaces resources or PUT to partially update resources. | Error | | :no_entry: **DO NOT** use patterns that require multiple round trips to complete a single logical action. | Warning | -| :ballot_box_with_check: **CONSIDER** supporting return and omit-nulls preferences. | Warning | +| :ballot_box_with_check: **CONSIDER** supporting return and omit-nulls preferences. | Warning | For a complete list of standard REST operations you can refer to the [Microsoft REST API From d9974f04d112121fe1da29a1b3618b5e254bfb92 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 8 Nov 2021 22:29:43 -0500 Subject: [PATCH 191/729] Editing and alignment --- graph/GuidelinesGraph.md | 42 ++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index cab6d373..924128a8 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -292,7 +292,7 @@ response pattern should be one of the first implementation decisions you make. | :no_entry: **DO NOT** use patterns that require multiple round trips to complete a single logical action. | Warning | | :ballot_box_with_check: **CONSIDER** supporting return and omit-nulls preferences. | Warning | -For a complete list of standard REST operations you can refer to the [Microsoft +For a complete list of standard HTTP operations you can refer to the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). @@ -327,8 +327,7 @@ and consistency by using recommended Graph error model: The following examples demonstrate error modeling for common use cases: - **Simple error**: A workload wants to report an error with top-level details - only. The library allows the workload to create the error object and just - specify the top-level error code, message and target (optional). + only. Then the error object contains the top-level error code, message and target (optional). ``` { "error": { @@ -339,10 +338,9 @@ The following examples demonstrate error modeling for common use cases: } ``` -- **Detailed error**: A workload wants to report an error and provide - service-specific details of the error via the innererror property of the +- **Detailed error**: An API needs to provide service-specific details of the error via the innererror property of the error object. The code property in innererror is optional but highly - recommended. It is intended to allow workloads to supply a service-specific + recommended. It is intended to allow services to supply a specific error code to help differentiate errors that share the same top-level error code but reported for different reasons. @@ -363,10 +361,10 @@ The following examples demonstrate error modeling for common use cases: |---------------------------------------------------------------------------------------------|---------| | :heavy_check_mark: **DO** return an error property with a child code property in all error responses. | Error | | :heavy_check_mark: **DO** return a 403 Forbidden error when insufficient scopes are present on the auth token. | Error | -| :ballot_box_with_check: **CONSIDER** returning a 404 Not found error if a 403 would result in information disclosure. | Warning | | :heavy_check_mark: **DO** return a 429 Too many requests error when the caller has exceeded throttling limits. | Error | +| :ballot_box_with_check: **CONSIDER** returning a 404 Not found error if a 403 would result in information disclosure. | Warning | -For a complete mapping of error codes to HTTP statuses please refer to the +For a complete mapping of error codes to HTTP statuses you can refer to the [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). ### API contract and non-backward compatible changes @@ -374,7 +372,6 @@ For a complete mapping of error codes to HTTP statuses please refer to the Microsoft Graph definition of breaking changes is based on the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change). - In general, making changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a breaking change. @@ -405,7 +402,7 @@ spec](https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-pr When changes are imminent you need to support explicit versioning as it's critical that clients can count on services to be stable over time, and it's critical that services can add features and make changes. Microsoft Graph API -follows the guidance described in the Model Versioning section in the [Microsoft +follows the guidance described in the Model Versioning section of the [Microsoft REST API guidelines](https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#12-versioning). @@ -455,7 +452,7 @@ is not necessary to mark members of that type as deprecated, nor is it necessary to annotate any usage of that type in entity sets, singletons, properties, navigation properties, functions, or actions. -Example of property annotation: +**Example of property annotation:** ``` @@ -479,17 +476,17 @@ date the element was marked as deprecated) and a Sunset header (with the date 2 years beyond the Deprecation date). Response also includes a link header pointing to the breaking changes page. -Deprecation header example: +**Deprecation header example:** ``` Deprecation: Thursday, 30 June 2022 11:59:59 GMT Sunset: Wed, 30 Mar 2022 23:59:59 GMT Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2020-06-30_state ; rel="deprecation"; type="text/html"; title="state" ``` -Deprecation cadence: +**Deprecation cadence:** - As an API developer you can mark individual API schema elements as - deprecated on a quarterly basis, after going through a API review and + deprecated on a quarterly basis, after going through an API review and approval process. Quarterly deprecation cadence will allow the services to evolve schemas over time, without waiting for a coordinated, monolithic endpoint change. @@ -507,15 +504,15 @@ Deprecation cadence: ## Common API Patterns -The guidelines in previous sections are intentionally high-level and provide a -jump start for Graph API design. More detailed design guidance on REST APIs is +The guidelines in previous sections are intentionally brief and provide a +jump start for a Graph API developer. More detailed design guidance on REST APIs is published at the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/) and Graph specific are outlined in the table below. **API Patterns** are design documents providing best practices for MS Graph API development. They are to serve as the means by which API teams discuss and come -to consensus on API guidance. +to consensus on API guidance and review API designs. You can find references in the most common patterns in the table below: @@ -533,17 +530,8 @@ You can find references in the most common patterns in the table below: | Long Running Operations | | | | Delta Queries | | | -These patterns are provided as instruction for API desiners to help write -simple, intuitive, and consistent APIs, and are used by API reviewers as a basis -for review comments. - -## Final thoughts - -These guidelines describe the upfront design considerations, technology building -blocks, and common patterns that teams encounter when building their Graph APIs. -The links below provide references to the foundational documentation on related -topics: +## References - [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/) From 3fad2e5e226caa9b8308d0084c916e0f4fc7250f Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 8 Nov 2021 22:34:03 -0500 Subject: [PATCH 192/729] reordering --- graph/GuidelinesGraph.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 924128a8..a5c5a823 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -203,10 +203,9 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **DO** support \$select, \$top, \$filter query options | | :heavy_check_mark: **DO** support \$filter with eq, ne operations on properties of entities in the requested entity set | -| :ballot_box_with_check: **CONSIDER** supporting \$skip, \$count | | :heavy_check_mark: **DO** use batch request to avoid too long query options | -| :heavy_check_mark: **DO** use request body with the content-type text/plain for POST queries | -| :heavy_check_mark: **DO** use request body with the content-type | +| :heavy_check_mark: **DO** use content-type text/plain for POST queries | +| :ballot_box_with_check: **CONSIDER** supporting \$skip, \$count | Limitations of \$query requests made to Microsoft Graph: @@ -232,10 +231,10 @@ Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conv | :heavy_check_mark: **DO** verify that the primary id of an entity type is string | | :heavy_check_mark: **DO** verify that the primary key must also be defined as a property. | | :heavy_check_mark: **DO** verify that the primary key is composed of a single property and not multiple. | -| :no_entry: **DO NOT** add the property id to a complex type | | :heavy_check_mark: **DO** use an object as the root of all JSON payloads. | | :heavy_check_mark: **DO** use a value property in the root object to return a collection. | | :heavy_check_mark: **DO** include @odata.type annotations when the type is ambiguous. | +| :no_entry: **DO NOT** add the property id to a complex type | ### Recommended Modeling Patterns From fba41629a39f54311c902b79fc4f747fcfced4fe Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 9 Nov 2021 10:24:41 -0500 Subject: [PATCH 193/729] Added formatting and patterns --- graph/GuidelinesGraph.md | 56 +++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index a5c5a823..3a6ee502 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -98,7 +98,7 @@ that the API will be discoverable and intuitive for developers, and consistent with other Graph resources. When resources are defined it’s time to think about the behavior of your API and -define required operations and actions. +define required operations and actions.There are read-only and write scenarios where a resource can be used to represent some kind of data processing operation. The terms function and action are used to identify read and write operation style resources, respectively. At every step of your design you need to consider security, privacy and compliance as an intrinsic components of your API implementation. And finally based @@ -113,32 +113,32 @@ appear in API URLs and payloads and should be descriptive and easy to understand. Therefore you should follow the rules in the table below: |Requirements|Example| |--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| -| :no_entry: **DO NOT** use redundant words in names. | Right: /places/{id}/**type** and /phones/{id}/**number** Wrong: /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | -| :no_entry: **DO NOT** use brand names in type or property names. | Right: chat Wrong: teamsChat | -| :no_entry: **DO NOT** use acronyms or abbreviations unless they are broadly understood. | Right: url or htmlSignature Wrong: msodsUrl or dlp | -| :heavy_check_mark: **DO** use singular nouns for type names. | Right: address Wrong: addresses | -| :heavy_check_mark: **DO** use plural nouns for collections (for listing a type or collection properties). | Right: addresses Wrong: address | -| :heavy_check_mark: **DO** pluralize the noun even when followed by an adjective (a "postpositive"). | Right: passersby or mothersInLaw Wrong: notaryPublics or motherInLaws | -| :heavy_check_mark: **DO** name property as “email” | Right: email Wrong: mail | +| :no_entry: **DO NOT** use redundant words in names. |- **Right:** /places/{id}/**type** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | +| :no_entry: **DO NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | +| :no_entry: **DO NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | +| :heavy_check_mark: **DO** use singular nouns for type names. | - **Right:** address
    - **Wrong** addresses | +| :heavy_check_mark: **DO** use plural nouns for collections (for listing a type or collection properties). | - **Right:** addresses
    - **Wrong** address | +| :heavy_check_mark: **DO** pluralize the noun even when followed by an adjective (a "postpositive"). | - **Right:** passersby or mothersInLaw
    - **Wrong** notaryPublics or motherInLaws | +| :heavy_check_mark: **DO** name property as “email” | - **Right:** email
    - **Wrong** mail | #### Casing |Requirements|Example| |--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **DO** use lower camel case for *all* names and namespaces | Right: automaticRepliesStatus. Wrong: kebab-case or snake_case. | -| :heavy_check_mark: **DO** case two-letter acronyms with the same case. | Right: ioLimit or totalIOAmount Wrong: iOLimit or totalIoAmount | -| :heavy_check_mark: **DO** case three+ letter acronyms the same as a normal word. | Right: fidoKey or oauthUrl Wrong: webHTML | -|:no_entry: **DO NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | Right: subcategory, geocoordinate or crosswalk Wrong: metaData, semiCircle or airPlane | -| :heavy_check_mark: **DO** capitalize within hyphenated and open (spaced) compound words. | Right: fiveYearOld, daughterInLaw or postOffice Wrong: paperclip or fullmoon | +| :heavy_check_mark: **DO** use lower camel case for *all* names and namespaces | - **Right:** automaticRepliesStatus.
    - **Wrong** kebab-case or snake_case. | +| :heavy_check_mark: **DO** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | +| :heavy_check_mark: **DO** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | +|:no_entry: **DO NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate or crosswalk
    - **Wrong** metaData, semiCircle or airPlane | +| :heavy_check_mark: **DO** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw or postOffice
    - **Wrong** paperclip or fullmoon | #### Prefixes and Suffixes |Requirements |Example| |----------------------------------------------------------|------------------------------------------------| | :heavy_check_mark: **DO** use namespaces | Microsoft Graph model types can be declared within a [type namespaces](./type-namespaces.md) to reduce the need to prefix types with a qualifier to ensure uniqueness. | -| :heavy_check_mark: **DO** suffix date and time properties with | Right: dueDate — an Edm.Date Right: createdDateTime — an Edm.DateTimeOffset Right: recurringMeetingTime — an Edm.TimeOfDay Wrong: dueOn or startTime Right: instead both above are an Edm.DateTimeOffset | -| :heavy_check_mark: **DO** use the Duration type for durations, but if using an int, append the units. | Right: passwordValidityPeriod — an Edm.Duration Right: passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable) Wrong: passwordValidityPeriod — an Edm.Int32| -|:no_entry: **DO NOT** use suffix property names with primitive type names unless the type is temporal.| Right: isEnabled or amount Wrong: enabledBool| -| :heavy_check_mark: **DO** prefix property names for properties concerning a different entity.| Right: siteWebUrl on driveItem, or userId on auditActor Wrong: webUrl on contact when its the companyWebUrl| -| :heavy_check_mark: **DO** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | • Right: isEnabled or isResourceAccount • Wrong: enabled or allowResourcAccount • Right: allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below • Wrong: isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | +| :heavy_check_mark: **DO** suffix date and time properties with | - **Right:** dueDate — an Edm.Date
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Wrong** dueOn or startTime -
    - **Right:** instead both above are an Edm.DateTimeOffset | +| :heavy_check_mark: **DO** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32| +|:no_entry: **DO NOT** use suffix property names with primitive type names unless the type is temporal.| - **Right:** isEnabled or amount
    - **Wrong** enabledBool| +| :heavy_check_mark: **DO** prefix property names for properties concerning a different entity.| - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl| +| :heavy_check_mark: **DO** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount •
    - **Wrong** enabled or allowResourcAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below •
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | ### Uniform Resource Locators (URLs) @@ -517,17 +517,19 @@ You can find references in the most common patterns in the table below: | Pattern | Description | Reference | |-------------------------|-------------|----------------------------------------------------------------------------------------------------------| -| Key Property | | [Key Property](./evolvable-enums.md) | +| Key Property |The ability to uniquely identify an object through the key | [Key Property](./evolvable-enums.md) | | Entity Type | | | | Complex Type | | | -| Shared Type | | | -| Type Hierarchy | | [Modeling with Subtypes](./Modelling%20with%20Subtypes%20Pattern.md) | -| Dictionary | | | -| Evolvable Enums | | | -| Type Namespace | | | -| Change Tracking | | | -| Long Running Operations | | | -| Delta Queries | | | +| Shared Type | The ability to reuse a type defined by another service. | | +| Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Modeling with Subtypes](./Modelling%20with%20Subtypes%20Pattern.md) | +| Dictionary | The ability to persist a variable number of properties. | +| Evolvable Enums | The ability to enable non-breaking changes for Enum type. | | +| Type Namespace | The ability to reduce the need to prefix types with a qualifier to ensure uniqueness. | +| Change Tracking | The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph | +| Long Running Operations |The ability to model asynch operations. | | +| Delta Queries | The ability to query changes in the data exposed by Microsoft Graph | | +|Navigation Properties | | +|Viewpoint | | ## References From d02a97cd81409492469916ba8c300b073ffb6f09 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 9 Nov 2021 10:50:11 -0500 Subject: [PATCH 194/729] Added TOC --- graph/GuidelinesGraph.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 3a6ee502..38234890 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -1,8 +1,28 @@ # Microsoft Graph REST API Guidelines Table of Contents - -[[_TOC_]] +- [Microsoft Graph REST API Guidelines](#microsoft-graph-rest-api-guidelines) + - [](#) + - [History](#history) + - [Introduction](#introduction) + - [Legend](#legend) + - [Design Approach](#design-approach) + - [Naming](#naming) + - [Casing](#casing) + - [Prefixes and Suffixes](#prefixes-and-suffixes) + - [Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) + - [Category](#category) + - [Query Support](#query-support) + - [Recommended Modeling Patterns](#recommended-modeling-patterns) + - [Behavior Modeling](#behavior-modeling) + - [HTTP Operations](#http-operations) + - [Microsoft Graph rules for modeling behavior](#microsoft-graph-rules-for-modeling-behavior) + - [Error Handling](#error-handling) + - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) + - [Versioning and Deprecation](#versioning-and-deprecation) + - [Deprecation Process](#deprecation-process) + - [Common API Patterns](#common-api-patterns) + - [References](#references) ## #### History @@ -194,7 +214,7 @@ enterprise perspectives and represents one of the following: Top-level API categories are aligned with documentation, developer tools, and in general are relatively stable. If a new category needs to be created, it requires an API REview and an API Council approval. -#### Query +### Query Support Microsoft Graph APIs should support basic query options in conformance with OData specifications and [Microsoft REST API @@ -275,7 +295,7 @@ its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. -#### Microsoft Graph rules for modeling behavior: +#### Microsoft Graph rules for modeling behavior |Requirements |Severity| |----------------------------------------------|----------------------------------------------| From 64cd39549665429cc678b5cc6c31a383039b6aa6 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 9 Nov 2021 17:03:51 -0500 Subject: [PATCH 195/729] Update graph/GuidelinesGraph.md Co-authored-by: Darrel --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 38234890..8be0daba 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -395,7 +395,7 @@ In general, making changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a breaking change. -:heavy_check_mark: **DO use not-breaking changes:** +:heavy_check_mark: **DO use non-breaking changes:** * Addition of an annotation OpenType="true" Addition of properties that are nullable or have a default value * Addition of a member to an evolvable enumeration 1. Removal, rename, or change to the type of an open extension * Removal, rename, or change to the type of an annotation *Introduction of paging to existing collections From 96633b1d2cba18f80953107debd838c4859a815e Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 12 Nov 2021 14:52:58 -0500 Subject: [PATCH 196/729] formatting --- graph/GuidelinesGraph.docx | Bin 56825 -> 0 bytes graph/GuidelinesGraph.md | 48 ++++++++++++++++--------------------- 2 files changed, 20 insertions(+), 28 deletions(-) delete mode 100644 graph/GuidelinesGraph.docx diff --git a/graph/GuidelinesGraph.docx b/graph/GuidelinesGraph.docx deleted file mode 100644 index cd4483bdcc92867b73af4cd6df4a97b4cb1717a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56825 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
  • !6cZSn9=b z0g&2Ob$uL8*vdi6CRESTOhOE76PC2wb{%0GgGP1FH|ibd^tOt)c{r2U25E&Mx$)%a zI=F_$Y5Dqdyyc>Wz2(+M7`ePn_fl*(lGg{is<^Ki}smv zw24cIS;OR-E^+2DtfaLwYpF1e zc`5!gzDKokpuFxHcZeB=#!STo-gv+%<}Uat1dZ`fuxeC?EsGp8jYzJoJK_Vkt3WNk zwW068B?y+4v8pe^V$6nOjH|+cC1* z57k?G4=`&6sNjBXUdC;;x3iDQGsStO!KWDlTfmIg=&7}fYBlG#&OQsSNV00Z@X|eJ zUH`Sh%leK!sR#xD5P|~$fcjmJ{ZrvNnHgIf)BS@P{#JMA>XNp|qS#%kr>^K1IPal2 z@etqx(Xccizx(Cvv9F?CL&h)5O$Noi%C^-?6qGd{a8Je6&q(mDA!-d5rlF?MTbeAM5;E}f#J&7yEycK zwB>VIPVK8wR>jhMK-&CuUd>VE>sdCZPO}3$45xZ@-2r`87R9*vi%=vXfTKRz6gLf< zasNCVxfATVK^#a*K1sku{Z)u&wBfrI`wb3&tW|F2KhI7gcn(|{fPn*13cCI6gyD1< zNpY0aK#T9pEwOOVDMF_-fO7xbKRS}d%}Z#Dk-hwMak$zX+0eyNQnf=uE6M(NUg(17 z5&hzN-C6mtU3XLO|9Tnw;rssCfb}Q5yLgM|{p_Z%c+30cN%jfnn#K=Y*ez#J==a$^ z#KjFdj3Wd-@Qnk?2gDrVfC`dCrk?a3pR2NxfG(b4FitZoh39qhFSKnS9o!8HigvuP zxY1p0Af2dPB)bsD#lv3nIc1u%;~;sY)}RP^#HNs&JV_T*(#{Qes1_-0YEDFol9n<2 zeRg6ToTI%a|Zthh`;&lUVxbR=~JfVMoD8 z$y1%6#T~deIIJP#0=fI{p!Q%6I>M$GezIQs)A=+~dBnFcu3snvW+_y~NbOn#9&q;E zBfWN8zY9<<=PchyVI?W0k%g`zg%+D6aoCGSB3A8THrSbt&Q}?jzwOBTjK{J|W9MIn zWU_WexNbFfVTU;F+I3{JCtoXxKfznS4o~ysZPgNS>b@evc~NoL%GH8`<#W@EbsNjr zSTz`_l~6<_DfjcZ=XVbe>=ntl@)(i8bTwi(kyj$y&bn>4Tx_X65e|2QwhaLsOEiwJXQ zMKaynhYTo5a9){ELY5q5assh^Ht9Gaz$>P)g>c13AEdZWX!X@YBc+*AUF@E-v>)7c zc2!v@O?co4kR((fSYS<#D`eEfK$I+cUCMyH2&rS}o{>tPGcc}rL~EeO8<>vYiG8-{ z8Ffm(xQQb);Dw5w{uR}a3)X?f+6F|KcaWhqhYZ+a?V_hbyI#&}(xo$E0anM{tKN84 zB~OZ`(Ts8QGgY0x!;3ZljVk>&)n~aBMbV9?N%*8zvvVbg$8jG8E&VLV>TR8fJyPVgDg7mgTw#Ho|Y zqB?qA>|c{EGZJ&<<$-Lz(4z;F>}_h;rpM>+NGyBjKO#JxO-=LTt0LU@?VOD}g{XpD zL(?Wc%=*Dy`5sx@9Yw|~hfz#is;MG-fN>-12of4e)I_B;eo8(LBg|~OAuVFWm~@Tq zhrDE?=2}lds>+a!Ut^lKDavT^X0aF3$LARz^&g@h8FUxL(`<@x*v7RKtKI&j)f8}V z8p~X1D<4aSn5Sx%!s%KzPAjIY>?azM*q+=sB^}ls8!k9M^!;vQ{%5zlGyfQ91`Ytg zUH||9@jtDQqp_2dxs9pg-xjD|OWhV*G~P#7?-RJ^C@<}aUk!J^<+1w~y(r3?*5${5 ze_I>og0y(%z~@ucX+YvJ9r!HFy8+8x*?43mCt=xlfGeI#)gM=H=4_D}Y$@+pF77k7 zXnPLJ?VgS=YgG@U+B)Zt8zw6igVJAzwVTCLL~$ zFV$PNwv|<`5g|Ss7o8YQAHm&k_8P99uZ|wB9hXI0culGi_8k|GeH+!7kFrbt436#^ zCSHy&?AA6il3EoZ1I(9$Zy#I-Chc8#XdfpzcabNOkK8gNd%N}Y@;YQixu}KbI5^*56CFOBp+kA8_0T?ssxRBn$}$5H!js94pHB#h$MLFp6Y#eW^5ivG#mqcurG^j zey;9|8!tv=;B8tDmb7UiauodhLHrQ`?0n}N7Iw!QlJhat_dFglb~P;e?ufng1J9=M zqaI(706c%--~lGqBl@Z7%|=#o{k-wxJH9J>g!0x*G@i|-^hUbZfcrkZX_K$U1FvgF zC+?rhK8r2c`}4Qx$su&byMCL7)~uYhnd$eZUHc}yr5xC}1!RUJ0v>prIHXje})|{8ym0^d}*MU#5gLA^KnwJCnIWXVLqUgysZ!_N8IrpPO ze)$?Zw$MlA3AA+;G-UHh*Bv zeq{=UN2V`jZEz>`?o!LQ_24W@Bs_4g`W$|oE3+Nxrm2tjty=vvi`hIE6`o;_H+NeX zaoYAU>x+Nzr(X>ZuCeH#V5nY7AK#3YZkUh=NDfF39%t*Dd8;eXT&)Q91{1N@tW&}{hZe{W3 zg7tj|D1a)29x6%nne}F{coLqNY6gSSLgOfo(PS-x-(j|?tbN+up{N2sB9GM=s&MEa z9p}YUZg?h%!wN6lrC|eA`s&@2rT|}A46J(?A6QwQ(bN`Ug-HAkkYQrna)#rF9DgsI z?=R{SgpK&WB(wieD??%fRE%wIP~#a{4)%hYQa2DXj((z8lmv|{Z5b+4#sQwUoD@Qm zgJ(|=5ioZwk~Exb*XoAg_|cBp)GE+vUmFKOGDM;Zr-<(!P8RcpDGzxDs9KKxhf~X5 zCoei))CtA{Vrf-m9Jnv3pNxeP)y#4ISc@JsSs1oZy1Ww1aR_N86?}3vbZSq1U~#xZ z0#FerwOnlrs4gl2>;z}t5?2W|^_1K}Q1CYlEDlMf_;{0OluQumemB_$VOwlR;T@AoMBs z{SYXF2Q8Ad6oAE1r{vcSUm%QIf^QKuK+3}esEQa|?=$iF`m50ywqUB*c@acNV-5@> z6$@FP3zSV_rWB+4d_?1Y8RS;usUajaCryLu#fyj^Y2(d)_n(f;LGCQc12s^k%Cu5%dVJ=lM zdze+b1xb14l0l1lvKL{ypBU4qsRuSDpRP69Pkho34E)8y zQ@YLXoVKXo<;i`hBhu1-Ilu}4j$?ci8?D!-V$(8MhH<;j>nx}{MAqrZ`qw|!o zLYbDX%0WVWz+u=8F4(l$mUIjQT?rO5sPXWN1W<&I@2OSgA2xp#Ajy6dM!k6AQWATd(KNl>7f-NK|p{&MKDvk2Jqyo1w z{8s!G&y*J_c6x;&z z(9KQ@vN>cQlV4;%ySF~eo?#Ed%x{@Vh8fNXLll=bFM$1MODYjT<%6u8OTg{`&t#_(L>AhWIP2?_iVq+mD%u#hRQ_LPV z0qRkHayf{r{h$7Aj4L9m-ZY%=fVW#r>LehIpaR6v&?@rEzswbD!K5-2a4nMx>ycF# z0{{58bQ9=nE)s4rgFw1xsL}B zyLG=2_t`7+iS>T~YpN-rmnfO?Lt!smAx_$R*@)={Mgls_i_ct($&7^7nX&zVk#mrN zj(+GiC$dA^CU@^uuv1a;!aDm?X=^t`LAj2P zJB?=DligeR(h5to??!LE!oI%XEX=88*($LZ>MotFrKh9V8DNPq#g00HKyZjY%tq%n z=yDp=@SHqPm2H1CPqlFj>tPH%S=L@89t-Y6i=HdhLE5N~i%wv#I*Rq()jkmStZ}{B zqzo}9q6;<*gJ0F#I?MmlJb0~iv8zdyqtw%K5UKZeqjX^)eIF#LMS!e;%wx{bl4mRD<(CwbG5Dj_2rh zZat(ttPZ%L{S<+ay4@6~5EKawu|6m43}bYOO#61*Q*2<5cW117ej8G)K9B|^0ibA| zoLhA&xG<@g;}6QE<&Z#1)aaf(%CbBiQ4N?UYzT^H7iy@@Kamg=%T%Hx?!l{Ah1Wn1 zWRyME(nbZh;BU`=gu784n6)US{A%L(bwnjv7!%E%NOw6$kA?Fyest`^oK8k=yN(;g zMxkQ|+_0Mr3|sbag%Vo+A&3JBd|Qg^&&701>~4@E<{VfNks4*W$2X6z;4#UtV~z-- z@*+ZruvottD$VrU+&XY@DZpI-Z-jh)=(AsVyMN&q>(4Z%Xy7bxIf5}ZMc;R-J z^2_zk#U-Y|d^!$fE`1_C;>-YxztL0@$JD>U7KVy>%A3V--AG`?etL;-M6{$!ilTWz zSI1;5ti^QYPcSU$!5KpKQKWG1u^XZWz0IG#D$rvdYDH%0^C1&1v#Ur`D0@mA# z5OYpFHA3+@B0EsQkrL~+1uiV7_Q4`IM%FgOQ{-N3>UAC{Lp`uh`8B$jj1nHTd{tlp z(#bm^<+<)lbZDm4Vp2(1NnAU<$X`lPvBzFF7T-%>Bpt89uQ}|9g{EljG?-Ox71eu1 zWE`H(-ZY-*ylchnxeHe`G4^ZgcvzpaeZ5%euKvwWacFto2@P0Ov{ z-HTRYr_;A69nv)wf`YwoN}25a(>ZM`qZ`85<$2?GZ&dFa3IP_X17cB}J?aQcrz@=j zqt~5WfeeO*VO;Xw(@QvEQiunim`!<6d7mxsk9_3G=5Uf7{w_f=X*tn|q|P3kvSCfV zK=NO&D#M4@DF6c+!F8Adl$B?aN=R=wjJP43CL|bso-8C0{#)wg>ZD;5!-G4n5)uSa zLzoJ}-L`)cCE(yRyh}!j+hq%3@J5R#`Ee>$j$FYoL(P?TlO&_OQIj0OmK>9;;BuU3 z@2g!)>OT1J6i}I(tE55)xQ9!X1|EPIm<^>16cjOzwM+HkOkD!qtfCSF#oimeomI9q z&oLAGvqS`dF8da9nJ$M^DY5O>E7Sf2f9A$3_Irm*T!Jo}_1Uv`DjY^B=zkvEbP}!hgJ8&+Nr)>=3o~)qNbEHmwom z|0p|g`Q)qg{q80YU7cTU-ms;VrTO<2*L%KNu1I&`eKvcN$)vLD?S|rf9;4lS!h1b< zqI<>*j+R>Hc3R`X>+&aAz>EPdo-%O#ab^1&a#ock5 zJvj7XIyJfce*JiddN|c9i^rkk%leYLS!I^iHK`3#5Z)wv)|L`u)5U?*iRtpT=CdZf z;gdPJtP78>$~IYVAhBvjpyPxW+08^DqWF;78ZW7YoAmHa!UokIqEgdRBu33lu74iZ z0~BqD^;pUnrV>Zxvs9Bg-u#mTGz{EBAosfb_@ zdU!{#nw01(aN}`!RbY4`1tAnH}X)X%llCWL26Fx7|92n20}eeG_)wK}#{KWBY2* zns8=b_T+%g;mb(>l&A5!3qJ(^Z|1Y#5903ocBgPS0|Fp@oADo~zKyfBfw9B4L**Zj zzV=x3-XQ7*_%JUs<}e|2%>zv61@&dhyzT$HS+FZ?U%SK-r2% zr<%8y??+5dZ`Jo}aY3swk$>QU+ST=m{m)-{F}}~QCngWE>x1SlJIW@84IS>Q(I!vwtr5K3Ytt=JYDBjCxL5{2!Hn=}K31dKtj007Iye}N zK`_Q0KWmM`)Gh2M(dzo6X*dj$BMl`hQw#I$B%*=6I>J;}M%tXyh1$-i?)S*UW^?dZ zb&Z4BW^C-+)M#CDm@?9}Gs4iyazH&28!V`a2Q%0FnIsnP+|0)?TZX^&#^d8Zn`J2FNb?m_w zuy#koEY~&f!FDz7sIZFE9bu%esXJw2He@*{IdDdp>ODk1)ud#tFjZ$=9X@HPs70NT z+hSe(eluGG@vvbyJWE_ieak%8uYXy#C448*=C#VMpm|k)yY|xb9K`?&sjv{ZkiE6; zntXS|v#xE^W^8-8wr`NhUo&3JxAM?3M)XmRmr7ec?WQ&4|Pl zZd%jEoN(dgB31?W^&U20%2!Kk$Y7pyQ%=XDR!{V8ikP#<6y-fi*;i3nd-t@#%{O)t zg7)6Pcw5SUm%6Q9r|FEq+dGiWVYBy^ws%Uyqp4aY$uRb|+10!>LTWuoJ23Pyi1VJ` zQob+jRHLgJh0sZ52Dima9&=l&|DyPu&wRb=`kcf5IxFXCjl3!yWAl}a_%z~E%6VW} zQ8hAKw_03j=8zhr!{gR%Ry+7Sav7~#!F!rHHSCdwZR>2U%~rwN@n~2({9|hxLCx&@ zp1EF`BCN;@e81gokc5#v=RB1@iQ*y}WJ{Mf=SbCdZc6cH?HPI31rAhb zla7)+aFXXY5)4H;hvl@|O)UctD`i-%*Gda%;^Seamo5m(1RMfX$Ii`6Uy;Unb*pX)yYxaYPZYVJuu26xzdf3K0yT}|0eKq|DhCU8LIo_;rrp^Zf??s=Zn^Plq zXDob!0@8VYQ6Tqw#t%0A(XXlVQ!jJlwvg2w2A*G|_^aT@4)OQr%-O+3#211Y_PvSk zQcRVZx_#f|x#nXe6~@!G$+|ka^N&xidJMAh=qctsiQio97-Uj`Z&U0tw;6RHKDTEv zWRWXIJRi=tSdlDiv%qzLO&E*Zw`=QUwD!8}qN~{RKmIRJ9f$;e0DVBsu+Qx@V7k1J z+u|C1F0o}_#2&km_er?B9-0wqvZu%!Z4P=07U{U)8MKox|QoBooXf4L1!l=wI|VX^NfV{`)R^mH3$R<1T=C2fqa2t!6<|v-ynoh@R#`)UR3eIW8WvcX#`SDGsYn& zYDM1(iS0gqfx@W>;D1BEUJ!&IggO3S{2y?SAS8x2^g(2Nq&CsyUm=RD zKL;1^BgGw}&h!ck1cD10VnE&0pe2Si`+i1x_fl!5x9z34qD%A!ckIU#{>@Mr585ki zVeDCIa*o zJB<6z;X=A0iv1-z+n^9Vu!?su@>m{&#(w$Zr~b@as(=}`z`A4m4jJ6^OQ$kt_rA?7 z1(v@p;CxCO`&{%fD1P%j;fH25Z(9V-^3&*wR9qaH7(Yl381=RD1_bDk?b zrePFEqh<$TaL(;=Kh1@7L%xY0(~3*X3;HYvU29AnlbKZXYsljsOvD-C+xs9A$hxrfs_~gSf^9iMq0V zI$zO-w!DT8=yFtNTg9bOd8mnEpzUZUy??TrtBP&=jLQPQ2UlQ0KMO=&6zO4))kIgte*w(V~ELj_ABd6DSD7_H00+h&|SkE_H zLqpFODvp}|v6vlF4nt@;=Qk+kxvbv@1E&gCIs4E?MHgW9z>py zDUW}QWK)PyNUrThR8MkFbI(-Vy8D214@|-XDjOPq!L<_kFH#&oE+>wDpW^BkEY>mR zD-EdB^aFrL^hnAkhihHL1o(Lz%|05IyGMfZ!hVYkwZvrAmyy_UpgZfD6?XXM@vy%5 ziLNJ(C;5uR0-bzcQ?P}jKz(8;{uS$I@RzGL`@;|A@5fc8K;WjQ>8r+ZbyzhNq}SSm zcCp5U-;P#g$jf^%l8-m2X0A~L#HFI#!HH7N<`6AD4m(2( zKSi20P@(N+S8P}iEeZwN+Lptby1voV`MfGE)qqU<)TII0X5Ab|i`1FqMp#oS0TA^t z+{%1u?5-()Ysq&pcle>=Akeh+JwP;&e_;Odd)T2iE$Q0h|JwGJtR6c}l}hc%)yUOK z+t?>~*l73@dC9ihQl#Yiew#l-ZjjyCP_#oO`;A9XXa7(L$SHJcMu|4+t(m#{$SmCs z#nIesYUU4ID>RpAKKn844R+LMZd{3+6972u10r?VvKQI@DA0Gdo0 zEeLljDHM|eODQ{BQ1&P(Qze{&JPg9eS|sEyCZQbodnAkkRI4 z8z9R2Ww_@aGI7@~3`rVY(5PzE!jvVm);0zttbAQ-kRp=Q8c<|thh7a-4T5xj8kzD& z1RJIo`o2>S!*6smt1tNYdc~>>o1msC14$XF82g8fmvYg^sV6~@GL|9r2$?98rVf{n z0^f~|mqT?33^ztir_Q$}Ln&DvM{^pB>~!xO3o7z`1A?A_;xGtux&UtKGzwBrU{Ndt zDRYn%eICWW-;1#YRGuo+is2SZu}ni%OgC=XhPPQbfjQKE$_!A4J@Qwciv1}#ZkMGf;+Rn1gCKOGpXZ8;%jJt_N)Hp7rYERrD;_gfL1Nrmp zr&7*k?kdq~{U@&TY}anp&ncHFWRJV}z2j^KWBnCP{Z53_#xq%sr#3SY{};u>HjimU zcYn^$@19J%J#l&_%s)Mue7`qQ4+SV`{?%edk6W*i!Ij5{ccQ3OM9Uf4l;CDL);O@^ zRABAOY+VVPSX2IA!H1!;um*#Wrq7eid(q@;d?xD{b8gIhLm+nOOqbLL!_3U0Mh*x} zGVBd5MHsIAs^E-!lc~I(kO*XNEVn+lb-KO=sfr0kUGwQ5M(1fJW(|iUlg_~1=^X`F zV2kHT>$24*F)VMDu?7=5KMU0AQs5{S&kF^^Sv*hvvVaPRWKaq32RX^mV`#Q2YLDJ2 z`x}8L7y@!JGDyE)@ZbW0YO>Mo?*43Y#-49<+pSc{UQHG*RN==hPh1Lf9!4myt0#*R zHY)ItO3$Z}x{9a+Q-GSe$-^(?8@DS<7^D&VC z`@*?>Wmt#>io7U#N`i&PV|B$qV2KzCdxLv}5%mRyVe@|nTZsup$*bso?Hes8eJ=T>d0sw&j&yd&A$=%BMpZNpl+M14g z|cR&~AK;qmfz$vzM@LYS zKkfackDP~I4ip)v?n-YD2TJN&^RdtSis(~36$aU%Yo~b~>j}Gr4?P>`6?J9!nB<-R zMI)C;I(g?A+uZpT3LWYQ>7BVVK`il(3+IO~P8J4V;HP=ZkGV@q@#J>u>2nphpZXpK z+~v1j%Em>LJuU`GIR%7Z9d1hR@#H>|66jgq+pNls(4{FO){AVS_Q`8{S@{`d(hgiH zdk1)ivTG8pi6NNBaQijpL?)25dl-fF#(3puN3OL+_yp-Oh61kj@LCY~ymJKqR1;&c zUUi;Bqkp{hoDEJyt5g5L{p<{#KLvUJIOVH5UFpYdCy%x>m4C-H>JaO8cSXA%7Y!C7 z@@xJD4QN*Lr(rz)PAz6ExH?S^V3{LEJf6Fns-Pr0ktBO@vYpk!R2oU9qa^!}WII~U zy@Fw9GeX3Ht)_6qiD4AgywS;Ks3e!ci%JduS?xNI!Zrg)3D*v!#7hej@{NYqIHx+l zJ3vWZ3C{cqLdagyDGYHj-pSB+bp4x~Ars<%7^??sF(f#Bq zPcQ62CpO7h)!>756*XV_090kVY1<6B&Q@iM+}$c6?HrHLePTnU=65x+Sj) zhAnjnukTRcrCF^nr+L!x#~v`1UD5y-Y#h|6UiHQxTVhtSK|%K-PoH5-3N-Zc&dg#x;V=>T?5{X-umzd3eCf~64usY$KH3OL@kfkw=e6{>=N zY*CCRRCBSr?x$@Ua0$FE>XMq-1?`8&{wPwNG1R7cgm^KGovjp!(@i0vh@#PFG)DgQ z3Z?qR;~!`qN49(vszbfPTeBafSe<5@M>P=_Tg-Q=pb;TEIP-Rzt}toZ@Bak;T0y02zG z!D~#R4euE$7wDPUrzcwrc;kPdIpvrbDmZo2w@i{es_%78`l*Zl)V^epX zwXJJR&`ut}z!`P5svl@>112>y7z#@+|nBe$iI z9KEL0cVhld*mCq`AWmER2i>h#4-R@8O=0^{mxjjLDsMg*u^x{_wX-ZndPDSaKicD4ImwGyJ4%V=~MGJhRmd+M}9+q zB;%*7P{LBnG&-_t68i>GSqc-Wv5pz*Id+pkKZ#CWe)&|2liP=zXRfPy&Z?VQv`krpSwIoCD5m&Vrv&VON@faDEEa+It7ZH@x=DQ~ZxeY8uJo2( z6k~ALmU4|(+!z$(yT`|1rqZp=T)^SJ-7C=TEEB*iD#o0YZG7 zfQ8W8-Cq7;pqallM}ESFYXXoLWnU89Rv|ux_>s)x;OK=bd;`9it{VkyqTy_PUpsW6D*zbQ~f37Y<*1T9ZPkw%~)KRw&yM5rND zPvYM+9c}?w#JxbMWcmHYhe7ZI<6TnZ+%EJ@~)ULcv82-$k zL+xODV8m-E*qf7#qMz6U^!z<+#~eeIxogqRj<;GZ-KqN`RkA}jids+mCR{GENPgG1 zV+m?#%)8)KZbzA$LQYXmXl+K9s!}E_k@QDYiUaXplt&^eBwpqiYa$5y zUAh@d9Bg%~^6~-jTAoiVlN7I4jIk9={HOElHU3!aQB9l`D|r)aMX7}sY{lpENzABC zbG&hk)>J`CV?{XkfIIgdbGj7&eJOB}>JMev7()S@5gb!|lYdtxAgl zV8l_#D;>?0l~4+(+Lyv)ujY$!j88bI2A%ciy_~wTcZGJBrQ4{-H19eg)9lmsW+2ns zmyN3Dh|KZCT^PRqZi{eb2061bZ3ljnj>+rw5T%{q#2l7|?^JAm?ECF1SGhIkIr~vu zIfODP>;78I9Ekefo)_YxwUSrd@BWISV(3Rj?EA{FpZW`1$s9uJoEEKAHoharM3I6k zAQ7OP%mP;bQIgL!Y%SShR~A)Gd*LOGpTBmBdI?fAC57v{ia+I~%Y5)`3r$zV_%tk1|lB<)^hH)?Q2I}S4hl5#Y3P19?eL{FMo$s`# zPoTP%nmg@47b|7g(Rogol}M5*@fVt4tp(e(balIdD)1&Iw?QcV(A*ECyF@asfj;4$ zY?xGL@X?l_vU0dy%u>vFy`7qxC*t5o*xx{>w5CH=I5M<|k|{FDu6s3U%}(jCOc;L((ZZF&};riCdoIRFR`KLe#mb>Ycg#j88~en}SnX zV4bM5C5$vO!0e<^7+Tw0~c zeDB1n?%eOGhj>$??$|^n?#A62^G=s^n27^eHsyV}?>4M(!x5iSkx$$)H7z7AL6r+Isiclym*F2Ik zD#oAXK#!K^=i<&k&aK*;*KJ*KmO(Y=!|Hu!VVKBqg-LkTw?5G+OZvf&<5-N$CaVC8 zZ!c2NSnzQ>h*`Q%U#k>>-LgeugkphV4_xd+ED&Sx;w{X5@2^Atpw$*vD$^v{be1Q7 zjC^@)oC_K~u-Dc-YoepfOV!B&eFWo<+K|F%m?h9Su23a7Jyw8oO8PDU@TQ>R~V469Hk;fQ{_bldU52J508FB-_ z-^*hVOpUQ}uga3QJMXUNVfR&|3T`al_ANS&i-3(Yw2q*aLM*`OVIU`-Q?ImCwCp1J zruchv#z#G=t;7-8wj1m8#4lNg>3Fa1+*s?rn*~886Vn}yLYk?O1_|xV-mva^9JpW4dn0WX8473i}Dn-+^8{sdZq>CG6Z7)H3VxRcVOD zIOxj^HZ?!TxdCnePTyZd5)iu}OPjV{i&d_|U)PB;_gm3YF5Z`oekpDi-4A1M|l zvPh6FJ6z-;>I*i{j;n>d686MP2&{jo9HARFOq=^&3gX8xQ5z5g{aU4yiE5X_$}mb; zRjWScFsT_Zmfft488m1~b!g28TpzX1qNho&1>8V-JZy?%D(!pDF#|po_Sm1V5)bOF zwoyAJK7~3QSRAVlfT_GJB9A*7?pGq|t#URrOupJkze`Z&L^X^w4;7;tKu13bUm1p4 zTJh`b#H)STRX`c^TK4YJT&W$rP&c6d4;%UCT?l1qNdhCht7h!;uKGD6zG-cTsDjnO zpC#m%6@)qXFAq6Qvb6SW`DD537#;s=_+<4G7lOgN9=qOl0#@F=6rM@w4DH*#u&p^5 zkTEZhl#DqGej`7B0B%9q4tl~t`n}0{Q*0R-G9s6c;BnLT%AEn7`ttagz1bX_;_nDr?JMKhj-v15>If5-+dV z>CFfHq8SFF2ifEE{R=+(VY|2cbzOVi@11kr=RP~<>S*n|Oa^D0*^I~{?30=f zFdTKu=i`Z@i^b#2fO5$BD4Q;tk20I2!brRy_)0YKn3vSFg>uu?FDSgW<*#SdW(yo< zJ)G9x>N)*u{b;cuu4Nh<05Ee004V;cA9>r{*8gJ&WhHCOWk!_b(K_Ka1JR@_a5kDh$lbG+(03peRm`RJG$;YtRXd8QP_q!{}e$4-|E!4fk%L^fX@Wq}6KE>d;u($LOX@ zd-_Isjgs6M>_V^xL4y?E;oa38H%b@V`rNEykXiHfn?`Wl6?fciZmcA#OP!-r-5p#> zz$*Q+C$ANBykI*)S*P9tc({i?*|9a#s@HbV@oH%+J3Y z7j{ko-)z>)X~LhPlxp1!tUL7?VT}$42Q-Au*|mwwJ_au|MM z;Z?X+$+iFa?&W(SV#nrYmyXPQU?;B|MjPT!s>hppkG~Krn4YrBn03s1Qt7weOJnod z6!c#>jhu{*bWWzymvk)=6}Rw`s>+L8nu5i0`|eCf#d41^OdJR(8NtP**(~}&QArg} znNzA5Djql18_3pE89J#;0!EVt4_V2#S9ly`>LZF|ES&DL)dee2%-sP60M{6%co~bb zsZsFQ^F+GMUaNac}?~;#J^vM}==P@a8)I%a0 zwq|Xb*hm?1Grgu*%voDPE}9m;+*i2UQDQ0 zeNZdoG6vn>G^irI?s^ni!&N4^i=ZTcBml@gph&_Y9W9F=$0Zmd;@q135c#Ol_$>rU z9>3Z{0#h5Fpu|HK1~zx69?LJ{d?2#m+z|2DT|Pk)4mDYUiApaoS{zCaPFcCum%2yx zMH)FQ80~lIvnI<`NKv0wMDd2g%$JfpauxugmwiY$IOPPKQmG3Ud4dwKDeh)BVk`m+ zu*1}XDw#^l5%M%oCdc}6jKd$=mL^JcjdC_V{DyhKPGyR;Gg37^(EK3na@fLi>u1PI z2B)Q#vyFqvx=%dc;LBKqIde3Xd70qzwZPeclERX}otY6RzD&kO6hS%SY$k`&`U~tlsW6?zBat&}ay2}T#pFncC(*4+F|NyuvsOkf zGRD_|+C9wI=iSXSPFlo}?hcI9T$fspXm0#CRQ9b>jt zd|jCqXy9M&YX?y~aTjDnaKNs+Z)8NIoMQg1rcPRWzYC5!(|Qqb)<$OQ>f&Jlx$R{0 zqm69fW{=A>KgIYH;ZqWu$*-R_IJgYBu=K=UFDQWNIDCi?00O22t)QPJYZuBY1^9^k=n$cQw~PlAVkU+&p{vF@kNoSiH0Cb&JUc(9kZ zKDm}0qUlx&9Dxr$L_(iAf{BA=k(vDYWlCz|nzr(S`AEZFtVgr!O1@z@-0=lMCZI;* zY5tW%kr*JJk~K~HZ1{UZ<+_?9EVT==6lQZ(26R<(5_TF_5qcLilY-N4=^_Ob4=CBx zO=M$kb0&3di%{s_v4&duQG6!l_5v`7J*>m&Yqy?z^r>6`b7Zs)T1=awi+re!%yfQn|}>A z&xG0tNhDzMQcRbVdU{Y%O{7rEyOV=q?&N13c0(GROhR|5mCLXO5xuBHsWRj|D1FMqQIVLhOFRL;amGh zwm919MOg7AH{Dn-c+^QvZ(tM^`L}>6u53m=fC~pL`oM+glmg1`+(>-GG_jl~2ALkN zVDv3otvn18ZK_gPdYJ)>@NN&N78DIVCO4q3iB{`(bes`d$z?41^!LA%h~f-QTu}|E z-%u_FA-j`Ak1>dZ0-pA{Eq!AD0z>miZR_{=XhwkmP z1j=StDDvy6W6(If&+!fLOVoP`Duz6Nj_E8x4VNHD1U-e8R&zCv^zj3yh6#A?9wvRRh|DdZ?;^mR$L z@c1gX3muNpCpr4XJS4O6uqOyEIk0*K37_jR-7C34#AQW`SDj{~nzVm%jC39%ZHTDAgj8Kdltv=Jq2* z|7)Z_Z4{8HujNX2m88*VDbRRTy2FcXtb$*6t_TKo=J8F;e%VolDE@xjfg@O*LkCUc#8$whaBh zw~00T;ul?|L`Cx#UziVBt#ntRPu&$E8u1-rhS$;{on{C-9B=K$D$97S&Ka5zBWzQJ z2S~h@$~$I^8+rNF`f3*bFJiOzvCDIIl&?ixSuCI#BsT5EB+YM!Ng z6T-nJ)O)|}8Mr8~F8xDi=1yw)o}g;jfRaFCCY(>A2Y4L4r_3EW0NDhWOyxSh@>#H^ ziVS)U3`6MG3(OQG4O>)eEI4k;a#fyNfq}8S&GDLlU(+eS`SDb3zLc; zEl=$+A!5Yz*dy`G7-gY^N(1lgVTj6oPUifzQdkI)i3!h9)x^T9P12AeVcKcN72>b< z5<^C}`&miEbyi}uv5@yX!D0J-ePbI86^Uj}h0v7r?(U~q4#%zaUZp9?CLeGW8NOe> z@?dVQKel0K<4fA(@3&?GmY?TBHRq$-uUQ21bkxpWg1P#2iFX2tcVmAQV7<&+^VJqhL`Kb`bEKrO+4M-LAfiTf?6h2cP*Wkf}fv%!;J zqG$gZKlxYj>{l51`5XhUh(e8%;7*-PO*GrNW_|&|NDDPMJ*3o$>uFWgG_87!9qcEY zz7}wMEO11g9_hYq3{wINye_muDe{1*87|(?#tk?+alndd?`XbG@##GSP0jecieFp1 zI>F`&c}cFKD1UUmYw74mY96B`{^TB231d#HY>&z{C-wmg&&HvHcNpV|#l#^&{?xRf z6KSNB=;J)^I%aPI>N%w_FDl9lg|CjSxgNi$^_3Tx!fBm+Q5;23@3?~q97pXEzpMZQ zlNVL!_(%7S1ejI#3}piuC@CoNKd$uGp{W8Y=|@T5Ss3`b+1vyFa2Sxrd4i)f8fI`sc+U+6UF!}=Ks0MIi0ry=|;$b&R+P(k2FGD!lULsdk(Mt{8fKi`KA A!T - **Wrong** /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | @@ -165,7 +168,7 @@ understand. Therefore you should follow the rules in the table below: A Uniform Resource Locator (URL) is how developers access the resources of your API. -Navigation path to Graph resources generally broken into multiple segments: +Navigation path to the Microsoft Graph resources generally broken into multiple segments: **{scheme}://{host}/{version}/{category}/{resourcePath}[?{query}]** where @@ -177,7 +180,7 @@ Navigation path to Graph resources generally broken into multiple segments: - **category** segment is modeled as an entity set or a singleton representing logical top-level API category; -- **resourcePath segment** can address an entity, collection of entities, +- **resourcePath** segment can address an entity, collection of entities, property or operation available for an entity. Structure of the resource path is covered in detail in the [OData Version 4.01. Part 2: URL Conventions](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html); @@ -186,9 +189,8 @@ Navigation path to Graph resources generally broken into multiple segments: and is covered in [Query](#query) section. While HTTP defines no constraints on how different resources are related -together, it does encourage the use of URL path segment hierarchies to convey a -relationship. In Microsoft Graph lifetime relationships between resources -supported by the notions of singletons, entitySets, entities, complex types and +together, it does encourage the use of URL path segment hierarchies to convey relationships. In Microsoft Graph lifetime relationships between resources are +supported by the OData concepts of singletons, entitySets, entities, complex types and navigation properties. #### Category @@ -212,8 +214,7 @@ enterprise perspectives and represents one of the following: - For example: /loadTestEntities >> Top-level API categories are aligned with documentation, developer tools, and in -general are relatively stable. If a new category needs to be created, it requires an API REview and an API Council approval. - +general are relatively stable. If a new category needs to be created, it requires an API Review and an API Council approval. ### Query Support Microsoft Graph APIs should support basic query options in conformance with @@ -238,10 +239,7 @@ Limitations of \$query requests made to Microsoft Graph: The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or -processing the request. One way to avoid this is wrapping the request in a batch -request, which has the penalty of needing to construct a well-formed batch -request body. An easier alternative for GET requests is to append /\$query to -the resource path of the URL, use the POST verb instead of GET, and pass the +processing the request. One way to avoid this is to use the POST verb instead of GET, and pass the query options part of the URL in the request body as described in the chapter [OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). @@ -277,7 +275,7 @@ of properties are three most often used patterns in Microsoft Graph today: also defines properties that are required/meaningful for the variant given by the type property. [Modelling with Flat Bag Pattern](./Modelling%20with%20Flat%20Bag%20Pattern.md) -The following table describes shows summary of main qualities for each pattern +The following table shows summary of main qualities for each pattern and will help to select a pattern preferred for your use case. | API qualities Patterns | Properties and behavior described in metadata | Suited for strongly typed languages | Simple query construction | Syntactical backward compatible | @@ -288,8 +286,6 @@ and will help to select a pattern preferred for your use case. ## Behavior Modeling -#### HTTP Operations - The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request / @@ -395,14 +391,14 @@ In general, making changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a breaking change. -:heavy_check_mark: **DO use not-breaking changes:** +** Non-breaking changes:** * Addition of an annotation OpenType="true" Addition of properties that are nullable or have a default value * Addition of a member to an evolvable enumeration 1. Removal, rename, or change to the type of an open extension * Removal, rename, or change to the type of an annotation *Introduction of paging to existing collections * Changes to error codes Changes to the order of properties * Changes to the length or format of opaque strings, such as resource IDs -:no_entry: **DO NOT use breaking changes:** +** Breaking changes:** * Changes to the URL or fundamental request/response associated with a resource * Changing semantics of resource representation * Removal, rename, or change to the type of a declared property @@ -521,19 +517,15 @@ Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="de bound by the quarterly cadence or minimal support period before removal of deprecated elements. -## Common API Patterns +## Recommended API Patterns The guidelines in previous sections are intentionally brief and provide a -jump start for a Graph API developer. More detailed design guidance on REST APIs is +jump start for Graph API developers. More detailed design guidance on REST APIs is published at the [Microsoft REST API -Guidelines](https://github.com/microsoft/api-guidelines/) and Graph specific are +Guidelines](https://github.com/microsoft/api-guidelines/) and Graph specific patterns are outlined in the table below. -**API Patterns** are design documents providing best practices for MS Graph API -development. They are to serve as the means by which API teams discuss and come -to consensus on API guidance and review API designs. - -You can find references in the most common patterns in the table below: +Recommended API Design patterns: | Pattern | Description | Reference | |-------------------------|-------------|----------------------------------------------------------------------------------------------------------| @@ -546,7 +538,7 @@ You can find references in the most common patterns in the table below: | Evolvable Enums | The ability to enable non-breaking changes for Enum type. | | | Type Namespace | The ability to reduce the need to prefix types with a qualifier to ensure uniqueness. | | Change Tracking | The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph | -| Long Running Operations |The ability to model asynch operations. | | +| Long Running Operations |The ability to model asynchronous operations. | | | Delta Queries | The ability to query changes in the data exposed by Microsoft Graph | | |Navigation Properties | | |Viewpoint | | From 6cceee36e6bc105cc14cf9cdc27350858d89a72a Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 17 Nov 2021 12:50:37 -0500 Subject: [PATCH 197/729] moving to RFC 2119 conventions --- graph/GuidelinesGraph.md | 40 +++-- graph/Modelling with Facets Pattern.md | 185 +++++++++++------------ graph/Modelling with Subtypes Pattern.md | 4 +- graph/~$delling with Facets Pattern.md | Bin 0 -> 162 bytes graph/~$delling with Subtypes Pattern.md | Bin 0 -> 162 bytes graph/~WRL0034.tmp | 134 ++++++++++++++++ 6 files changed, 246 insertions(+), 117 deletions(-) create mode 100644 graph/~$delling with Facets Pattern.md create mode 100644 graph/~$delling with Subtypes Pattern.md create mode 100644 graph/~WRL0034.tmp diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 01dd2e53..07c6a01a 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -133,35 +133,33 @@ identify potential error scenarios with secure and descriptive messaging. Consistent naming is foundational for API usability. API resources are typically described by nouns. You need to consider that resources and property names appear in API URLs and payloads and should be descriptive and easy to -understand. Therefore you should satisfy the following requirements summarized in the table below: +understand. Microsoft Graph naming conventions follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md). Below is a short summary of the most often used conventions. |Requirements|Example| |--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| -| :no_entry: **DO NOT** use redundant words in names. |- **Right:** /places/{id}/**type** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | -| :no_entry: **DO NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | -| :no_entry: **DO NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | -| :heavy_check_mark: **DO** use singular nouns for type names. | - **Right:** address
    - **Wrong** addresses | -| :heavy_check_mark: **DO** use plural nouns for collections (for listing a type or collection properties). | - **Right:** addresses
    - **Wrong** address | -| :heavy_check_mark: **DO** pluralize the noun even when followed by an adjective (a "postpositive"). | - **Right:** passersby or mothersInLaw
    - **Wrong** notaryPublics or motherInLaws | -| :heavy_check_mark: **DO** name property as “email” | - **Right:** email
    - **Wrong** mail | +| :no_entry: **MUST NOT** use redundant words in names. |- **Right:** /places/{id}/**type** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | +| :no_entry: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | +| :no_entry: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | +| :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong** addresses | +| :heavy_check_mark: **MUST** use plural nouns for collections (for listing a type or collection properties). | - **Right:** addresses
    - **Wrong** address | +| :heavy_check_mark: **SHOULD** pluralize the noun even when followed by an adjective (a "postpositive"). | - **Right:** passersby or mothersInLaw
    - **Wrong** notaryPublics or motherInLaws | #### Casing |Requirements|Example| |--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **DO** use lower camel case for *all* names and namespaces | - **Right:** automaticRepliesStatus.
    - **Wrong** kebab-case or snake_case. | -| :heavy_check_mark: **DO** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | -| :heavy_check_mark: **DO** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | -|:no_entry: **DO NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate or crosswalk
    - **Wrong** metaData, semiCircle or airPlane | -| :heavy_check_mark: **DO** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw or postOffice
    - **Wrong** paperclip or fullmoon | +| :heavy_check_mark: **Must** use lower camel case for *all* names and namespaces | - **Right:** automaticRepliesStatus.
    - **Wrong** kebab-case or snake_case. | +| :heavy_check_mark: **Should** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | +| :heavy_check_mark: **Should** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | +|:no_entry: **Must NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate or crosswalk
    - **Wrong** metaData, semiCircle or airPlane | +| :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw or postOffice
    - **Wrong** paperclip or fullmoon | #### Prefixes and Suffixes |Requirements |Example| |----------------------------------------------------------|------------------------------------------------| -| :heavy_check_mark: **DO** use namespaces | Microsoft Graph model types can be declared within a [type namespaces](./type-namespaces.md) to reduce the need to prefix types with a qualifier to ensure uniqueness. | -| :heavy_check_mark: **DO** suffix date and time properties with | - **Right:** dueDate — an Edm.Date
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Wrong** dueOn or startTime -
    - **Right:** instead both above are an Edm.DateTimeOffset | -| :heavy_check_mark: **DO** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32| -|:no_entry: **DO NOT** use suffix property names with primitive type names unless the type is temporal.| - **Right:** isEnabled or amount
    - **Wrong** enabledBool| -| :heavy_check_mark: **DO** prefix property names for properties concerning a different entity.| - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl| -| :heavy_check_mark: **DO** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount •
    - **Wrong** enabled or allowResourcAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below •
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | +| :heavy_check_mark: **MUST** suffix date and time properties with | - **Right:** dueDate — an Edm.Date
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Wrong** dueOn or startTime -
    - **Right:** instead both above are an Edm.DateTimeOffset | +| :heavy_check_mark: **Should** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32| +|:no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal.| - **Right:** isEnabled or amount
    - **Wrong** enabledBool| +| :heavy_check_mark: **Should** prefix property names for properties concerning a different entity.| - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl| +| :heavy_check_mark: **Should** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount •
    - **Wrong** enabled or allowResourcAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below •
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | ### Uniform Resource Locators (URLs) @@ -170,7 +168,7 @@ API. Navigation path to the Microsoft Graph resources generally broken into multiple segments: -**{scheme}://{host}/{version}/{category}/{resourcePath}[?{query}]** where +**{scheme}://{host}/{version}/{category}/[{pathSegment}][?{query}]** where - **scheme and host segments** are always [https://graph.microsoft.com](https://graph.microsoft.com/v1.0/users); @@ -183,7 +181,7 @@ Navigation path to the Microsoft Graph resources generally broken into multiple - **resourcePath** segment can address an entity, collection of entities, property or operation available for an entity. Structure of the resource path is covered in detail in the [OData Version 4.01. Part 2: URL - Conventions](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html); + Conventions](https://github.com/microsoft/api-guidelines/blob/op-graphPatterns/Guidelines.md#93-collection-url-patterns); - **query string** must follow the OData standard for query representations and is covered in [Query](#query) section. diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md index 136f0f1e..7e03418f 100644 --- a/graph/Modelling with Facets Pattern.md +++ b/graph/Modelling with Facets Pattern.md @@ -1,121 +1,118 @@ -# Type Hierarchy +# Facets Pattern Microsoft Graph API Design Pattern -  +## *A frequent pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with common properties and one facet property (of complex type) per variant.* -*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +## Context +Let’s assume you need to create an API to manage documents, pictures, files of +different formats which are organized in different folder hierarchies across +multiple local and shared drives. These items have many common properties such +as Name, Owner, Creation Date, common relationships like activities and +subscriptions, and a common set of behaviors like CRUD operations and sharing. -## Context +There are also subsets of values that are specific for each variant, for example +hashes and Mime type for files and eight different properties for photo like +camera model and settings. + +Since usually individual users or organizations deal with a vast amount of +information stored in files there is a need for easy filtering and querying +information based on its metadata. -Let’s assume you need to model an API to manage groups in an -organization, where employees can create groups and become owners of the -group by default. At the same time to support business processes some -groups may be created automatically by daemon applications using a -service principal account. In this case the service principle will -become the group owner. People and service principles have some common -and some unique properties such as both have unique identifiers and -credentials, but users will have additional properties such as email and -manager for example. Conversely a service principle won’t have a manager -assigned but may have an associated application identifier and a -description. - -Additional requirement for the API model is to be flexible and -accommodate future needs like a device may become the owner of a group. +While modeling for existing requirements we need to create a flexible API design +to be able accommodate future needs like new metadata or behavior. ## Problem -How to model group owners to allow a heterogeneous collection of users -and principles so that API design can reflect common and relevant -properties for each subset of owners and accommodate new owner types in -future? +How to model files and folders as API resources to be able to easily mange them, +query and filter using metadata, and + +A more general problem is how to model a collection of heterogeneous elements +that have a set of common properties and behaviors, and some unique properties +for each variant. ## Solution --------- -OData allows us to design collections of entities (entity sets, multi -valued navigation properties) with values of different types using -**type hierarchy**, where there is one abstract base type with a few -common properties and one sub-type for each variant of the entity. In -the current version of Microsoft Graph there are many collections of -items that represent slightly different things, variants of one concept. +OData allows us to design collections of entities (entity sets, multi valued +navigation properties) with values of different types using **type hierarchy**, +where there is one abstract base type with a few common properties and one +sub-type for each variant of the entity. In the current version of Microsoft +Graph there are many collections of items that represent slightly different +things, variants of one concept. ## Issues and Considerations -------------------------- - -When introducing a new subtype, you need to ensure that the new subtype -doesn't change the semantic of the type hierarchy with it's implicit -constraints. - -There are a **few potential risks** for client applications when new -sub-types are introduced: - -- De-serialization code might break because of missing -properties in returned collection items. Even though property X was -mandatory on all subtypes previously returned, the new subtype might not -have this property and the client code needs to deal with that. - -- Client libraries for strongly typed language might ignore some -of the values in the @odata.type property without further configuration -and need to be configured to be able to pick the right (client) type to -deserialize into. - -To minimize impact on clients type hierarchy can be refined by -annotating the collections with OData derived type constraints (see -validation vocabulary). This annotation restricts the values to certain -sub-trees of an inheritance hierarchy. It makes it very explicit that -the collection only contains elements of some of the subtypes and helps -to not return objects of a type that is semantically not suitable. In -addition, you can follow some of the mitigation techniques such as: - -- Avoid overgeneralized base types - -- Think about roll-out sequence - - Consider that Microsoft Graph does not return objects from a workload -that has a type that is not configured in current metadata. To avoid -inconsistencies, follow a two-step process: - - Introduce the entity type to the Graph metadata but don’t -return objects of the type in any of the heterogeneous collections. - - Enable your workload to return objects of the new type as items -of collection. - - -- Allow time for testing - - Inform the clients about the change and allow them to test the -changes in beta. Time is required to implement the code necessary to -deal with the new entity type, both in terms of de-serialization as well -as integrating it into the rest of the application. - -- Communicate the change in semantics - - - It is necessary for the client developers to incorporate the new -semantic into their application/service, even if the change is perceived -to be small. This requires early communication and clear documentation -of what the new type represents and why/how it is considered a subtype -of the original abstract type of the collection. + +When introducing a new subtype, you need to ensure that the new subtype doesn't +change the semantic of the type hierarchy with it's implicit constraints. + +There are a **few potential risks** for client applications when new sub-types +are introduced: + +- De-serialization code might break because of missing properties in returned + collection items. Even though property X was mandatory on all subtypes + previously returned, the new subtype might not have this property and the + client code needs to deal with that. + +- Client libraries for strongly typed language might ignore some of the values + in the @odata.type property without further configuration and need to be + configured to be able to pick the right (client) type to deserialize into. + +To minimize impact on clients type hierarchy can be refined by annotating the +collections with OData derived type constraints (see validation vocabulary). +This annotation restricts the values to certain sub-trees of an inheritance +hierarchy. It makes it very explicit that the collection only contains elements +of some of the subtypes and helps to not return objects of a type that is +semantically not suitable. In addition, you can follow some of the mitigation +techniques such as: + +- Avoid overgeneralized base types + +- Think about roll-out sequence + + - Consider that Microsoft Graph does not return objects from a workload + that has a type that is not configured in current metadata. To avoid + inconsistencies, follow a two-step process: + + - Introduce the entity type to the Graph metadata but don’t return objects + of the type in any of the heterogeneous collections. + + - Enable your workload to return objects of the new type as items of + collection. + +- Allow time for testing + + - Inform the clients about the change and allow them to test the changes + in beta. Time is required to implement the code necessary to deal with + the new entity type, both in terms of de-serialization as well as + integrating it into the rest of the application. + +- Communicate the change in semantics + + - It is necessary for the client developers to incorporate the new + semantic into their application/service, even if the change is perceived + to be small. This requires early communication and clear documentation + of what the new type represents and why/how it is considered a subtype + of the original abstract type of the collection. ## When to Use this Pattern ------------------------- -The Type hierarchy pattern is well familiar to OOP developers and well -suited for strongly typed client programming languages. +The Type hierarchy pattern is well familiar to OOP developers and well suited +for strongly typed client programming languages. There are related patterns to consider such as -[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) -and [Flat bag of +[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat +bag of properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). ## Example -------- GET -[https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners](https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners)  -returns a collection where each element can be a user or a service -principal, and has an additional property @odata.type to show subtype -for each variant: -``` + +returns a collection where each element can be a user or a service principal, +and has an additional property @odata.type to show subtype for each variant: + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {     "@odata.context": "https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", @@ -128,4 +125,4 @@ for each variant:         }     ] } -``` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index 136f0f1e..1991ecf9 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -22,8 +22,6 @@ manager for example. Conversely a service principle won’t have a manager assigned but may have an associated application identifier and a description. -Additional requirement for the API model is to be flexible and -accommodate future needs like a device may become the owner of a group. ## Problem @@ -32,6 +30,8 @@ and principles so that API design can reflect common and relevant properties for each subset of owners and accommodate new owner types in future? +A more general problem is how to model a collection of heterogeneous elements that have a set of common properties and behaviors, and some unique properties for each variant. Furthermore, it should be straightforward to add new variants to the API as needed in the future. + ## Solution -------- diff --git a/graph/~$delling with Facets Pattern.md b/graph/~$delling with Facets Pattern.md new file mode 100644 index 0000000000000000000000000000000000000000..817e118875072ab276dab2a4f0a5422a6d399ded GIT binary patch literal 162 zcmd<{&q+^I2*^*#qz&SxMF@GpEk|@@o?c=Q1#S;cD2(aQiwVOjq5tUFiUZVjXt? literal 0 HcmV?d00001 diff --git a/graph/~WRL0034.tmp b/graph/~WRL0034.tmp new file mode 100644 index 00000000..aba38c33 --- /dev/null +++ b/graph/~WRL0034.tmp @@ -0,0 +1,134 @@ +# Facets Pattern + +Microsoft Graph API Design Pattern + +  + +*A frequent pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with common properties and one facet property (of complex type) per variant.* +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +## Context + +Let’s assume you need to model an API to manage documents, pictures, files of different formats which are organized in different folder hierarchies across multiple local and shared drives. + +where employees can create groups and become owners of the +group by default. At the same time to support business processes some +groups may be created automatically by daemon applications using a +service principal account. In this case the service principle will +become the group owner. People and service principles have some common +and some unique properties such as both have unique identifiers and +credentials, but users will have additional properties such as email and +manager for example. Conversely a service principle won’t have a manager +assigned but may have an associated application identifier and a +description. + +Additional requirement for the API model is to be flexible and +accommodate future needs like a device may become the owner of a group. + +## Problem + +How to model group owners to allow a heterogeneous collection of users +and principles so that API design can reflect common and relevant +properties for each subset of owners and accommodate new owner types in +future? + +A more general problem is how to model a collection of heterogeneous elements that have a set of common properties and behaviors, and some unique properties for each variant. Furthermore, it should be straightforward to add new variants to the API as needed in the future. + +## Solution +-------- + +OData allows us to design collections of entities (entity sets, multi +valued navigation properties) with values of different types using +**type hierarchy**, where there is one abstract base type with a few +common properties and one sub-type for each variant of the entity. In +the current version of Microsoft Graph there are many collections of +items that represent slightly different things, variants of one concept. + +## Issues and Considerations +------------------------- + +When introducing a new subtype, you need to ensure that the new subtype +doesn't change the semantic of the type hierarchy with it's implicit +constraints. + +There are a **few potential risks** for client applications when new +sub-types are introduced: + +- De-serialization code might break because of missing +properties in returned collection items. Even though property X was +mandatory on all subtypes previously returned, the new subtype might not +have this property and the client code needs to deal with that. + +- Client libraries for strongly typed language might ignore some +of the values in the @odata.type property without further configuration +and need to be configured to be able to pick the right (client) type to +deserialize into. + +To minimize impact on clients type hierarchy can be refined by +annotating the collections with OData derived type constraints (see +validation vocabulary). This annotation restricts the values to certain +sub-trees of an inheritance hierarchy. It makes it very explicit that +the collection only contains elements of some of the subtypes and helps +to not return objects of a type that is semantically not suitable. In +addition, you can follow some of the mitigation techniques such as: + +- Avoid overgeneralized base types + +- Think about roll-out sequence + - Consider that Microsoft Graph does not return objects from a workload +that has a type that is not configured in current metadata. To avoid +inconsistencies, follow a two-step process: + - Introduce the entity type to the Graph metadata but don’t +return objects of the type in any of the heterogeneous collections. + - Enable your workload to return objects of the new type as items +of collection. + + +- Allow time for testing + - Inform the clients about the change and allow them to test the +changes in beta. Time is required to implement the code necessary to +deal with the new entity type, both in terms of de-serialization as well +as integrating it into the rest of the application. + +- Communicate the change in semantics + + - It is necessary for the client developers to incorporate the new +semantic into their application/service, even if the change is perceived +to be small. This requires early communication and clear documentation +of what the new type represents and why/how it is considered a subtype +of the original abstract type of the collection. + +## When to Use this Pattern +------------------------ + +The Type hierarchy pattern is well familiar to OOP developers and well +suited for strongly typed client programming languages. + +There are related patterns to consider such as +[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) +and [Flat bag of +properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). + +## Example +------- + +GET +[https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners](https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners)  +returns a collection where each element can be a user or a service +principal, and has an additional property @odata.type to show subtype +for each variant: +``` +{ +    "@odata.context": +"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", +    "value": [ +        { +            "@**odata.type**": "\#**microsoft.graph.user**", +            "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", +            "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" +            // ... +        } +    ] +} +``` From fd9e1c604cd92d81d09384f66db592c2403c4b8f Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 19 Nov 2021 16:12:18 -0500 Subject: [PATCH 198/729] resource modeling --- graph/GuidelinesGraph.md | 375 +++++++++++++++++++++------------------ 1 file changed, 205 insertions(+), 170 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 07c6a01a..65d4a604 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -1,6 +1,7 @@ # Microsoft Graph REST API Guidelines Table of Contents + - [Microsoft Graph REST API Guidelines](#microsoft-graph-rest-api-guidelines) - [](#) - [History](#history) @@ -8,10 +9,7 @@ Table of Contents - [Legend](#legend) - [Design Approach](#design-approach) - [Naming](#naming) - - [Casing](#casing) - - [Prefixes and Suffixes](#prefixes-and-suffixes) - [Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) - - [Category](#category) - [Query Support](#query-support) - [Recommended Modeling Patterns](#recommended-modeling-patterns) - [Behavior Modeling](#behavior-modeling) @@ -24,6 +22,7 @@ Table of Contents - [References](#references) ## + #### History | Date | Notes | @@ -33,8 +32,11 @@ Table of Contents ## Introduction -When building a digital ecosystem you should use API-first approach and start with design and development of your APIs. Considering API usability and creating APIs that are easy to discover, simple to use, fit to purpose, and consistent across your products will make the -difference between success and failure of your ecosystem. +When building a digital ecosystem you should use API-first approach and start +with design and development of your APIs. Considering API usability and creating +APIs that are easy to discover, simple to use, fit to purpose, and consistent +across your products will make the difference between success and failure of +your ecosystem. This document offers guidance that Graph API developer teams MUST follow to ensure that customers have a great experience. A new API design should meet the @@ -54,9 +56,8 @@ guidelines. Readers are assumed also be reading the Microsoft REST API guidelines and be familiar with them. Graph guidance is a superset of the Microsoft API guidelines and services should follow them except where this document outlines specific differences or exceptions to those guidelines. -Together these guidelines and a library of API patterns serve as the means by which API teams discuss and come -to consensus on API review recommendations. - +Together these guidelines and a library of API patterns serve as the means by +which API teams discuss and come to consensus on API review recommendations. This document borrows heavily from multiple public sources such as: @@ -66,7 +67,7 @@ This document borrows heavily from multiple public sources such as: 3. WSO2 Rest API Design Guidelines and others. -Technology and software is constantly changing and evolving, and as such, this +Technology and software are constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. @@ -75,12 +76,15 @@ a change or propose a new idea. This document offers prescriptive guidance labeled as follows: -:heavy_check_mark: **DO** satisfy this specification. If not following this advice, you MUST disclose your reason during the Graph API review. +:heavy_check_mark: **DO** satisfy this specification. If not following this +advice, you MUST disclose your reason during the Graph API review. -:no_entry: **DO NOT** use this pattern. If not following this advice, you MUST disclose your reason during the Graph API review. - -:ballot_box_with_check: **YOU SHOULD** fulfill this specification. If not following this advice, you MUST disclose your reason during the Graph API review. +:no_entry: **DO NOT** use this pattern. If not following this advice, you MUST +disclose your reason during the Graph API review. +:ballot_box_with_check: **YOU SHOULD** fulfill this specification. If not +following this advice, you MUST disclose your reason during the Graph API +review. ## Design Approach @@ -111,62 +115,64 @@ In general API design includes the following steps: - Specify errors To create a good API you need to start with understanding your **use cases** and -supporting domain model. We describe domain models in terms of entities or resources, their -properties, and relationships and further refer to it as entity data model. -There is no one-to-one correspondence between domain model elements and API -resources as APIs usually support only customer-facing use cases. +supporting domain model. We describe domain models in terms of entities or +resources, their properties, and relationships and further refer to it as entity +data model. There is no one-to-one correspondence between domain model elements +and API resources as APIs usually support only customer-facing use cases. After API resources are identified you need to name them and their properties so that the API will be discoverable and intuitive for developers, and consistent with other Graph resources. When resources are defined it’s time to think about the behavior of your API and -define required operations and actions.There are read-only and write scenarios where a resource can be used to represent some kind of data processing operation. The terms function and action are used to identify read and write operation style resources, respectively. +define required operations and actions. There are read-only and write scenarios +where a resource can be used to represent some kind of data processing +operation. The terms function and action are used to identify read and write +operation style resources, respectively. At every step of your design you need to consider security, privacy and -compliance as an intrinsic components of your API implementation. And finally based -on your API resources, their behavior, and anticipated exceptions you need to -identify potential error scenarios with secure and descriptive messaging. +compliance as an intrinsic components of your API implementation. And finally +based on your API resources, their behavior, and anticipated exceptions you need +to identify potential error scenarios with secure and descriptive messaging. ### Naming Consistent naming is foundational for API usability. API resources are typically described by nouns. You need to consider that resources and property names appear in API URLs and payloads and should be descriptive and easy to -understand. Microsoft Graph naming conventions follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md). Below is a short summary of the most often used conventions. -|Requirements|Example| -|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| -| :no_entry: **MUST NOT** use redundant words in names. |- **Right:** /places/{id}/**type** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | +understand. Microsoft Graph naming conventions follow [Microsoft REST API +Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md). +Below is a short summary of the most often used conventions. + +| Requirements | Example | +|---------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| :no_entry: **MUST NOT** use redundant words in names. |- **Right:** /places/{id}/**type** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | | :no_entry: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | | :no_entry: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | | :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong** addresses | | :heavy_check_mark: **MUST** use plural nouns for collections (for listing a type or collection properties). | - **Right:** addresses
    - **Wrong** address | | :heavy_check_mark: **SHOULD** pluralize the noun even when followed by an adjective (a "postpositive"). | - **Right:** passersby or mothersInLaw
    - **Wrong** notaryPublics or motherInLaws | - -#### Casing -|Requirements|Example| -|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **Must** use lower camel case for *all* names and namespaces | - **Right:** automaticRepliesStatus.
    - **Wrong** kebab-case or snake_case. | -| :heavy_check_mark: **Should** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | -| :heavy_check_mark: **Should** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | -|:no_entry: **Must NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate or crosswalk
    - **Wrong** metaData, semiCircle or airPlane | -| :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw or postOffice
    - **Wrong** paperclip or fullmoon | - -#### Prefixes and Suffixes -|Requirements |Example| -|----------------------------------------------------------|------------------------------------------------| -| :heavy_check_mark: **MUST** suffix date and time properties with | - **Right:** dueDate — an Edm.Date
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Wrong** dueOn or startTime -
    - **Right:** instead both above are an Edm.DateTimeOffset | -| :heavy_check_mark: **Should** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32| -|:no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal.| - **Right:** isEnabled or amount
    - **Wrong** enabledBool| -| :heavy_check_mark: **Should** prefix property names for properties concerning a different entity.| - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl| -| :heavy_check_mark: **Should** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount •
    - **Wrong** enabled or allowResourcAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below •
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | +| **casing** | | +| :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces | - **Right:** automaticRepliesStatus.
    - **Wrong** kebab-case or snake_case. | +| :heavy_check_mark: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | +| :heavy_check_mark: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | +| :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate or crosswalk
    - **Wrong** metaData, semiCircle or airPlane | +| :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw or postOffice
    - **Wrong** paperclip or fullmoon | +| **prefixes and suffixes** | | +| :heavy_check_mark: **MUST** suffix date and time properties with | - **Right:** dueDate — an Edm.Date
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Wrong** dueOn or startTime -
    - **Right:** instead both above are an Edm.DateTimeOffset | +| :heavy_check_mark: **SHOULD** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32 | +| :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong** enabledBool | +| :heavy_check_mark: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl | +| :heavy_check_mark: **SHOULD** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong** enabled or allowResourcAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | +| :no_entry: **MUST NOT** use 'collection', 'response', 'request ' suffixes . |- **Right:** addresses
    - **Wrong** addressCollection | ### Uniform Resource Locators (URLs) A Uniform Resource Locator (URL) is how developers access the resources of your API. -Navigation path to the Microsoft Graph resources generally broken into multiple segments: +Navigation path to the Microsoft Graph resources generally broken into multiple +segments: **{scheme}://{host}/{version}/{category}/[{pathSegment}][?{query}]** where @@ -175,44 +181,50 @@ Navigation path to the Microsoft Graph resources generally broken into multiple - **version** can be V1.0 or beta; -- **category** segment is modeled as an entity set or a singleton representing - logical top-level API category; +- **category** segment is a logical grouping of APIs into top-level + categories; -- **resourcePath** segment can address an entity, collection of entities, - property or operation available for an entity. Structure of the resource - path is covered in detail in the [OData Version 4.01. Part 2: URL - Conventions](https://github.com/microsoft/api-guidelines/blob/op-graphPatterns/Guidelines.md#93-collection-url-patterns); +- **pathSegment** is the last navigation segment which can address an entity, + collection of entities, property or operation available for an entity - **query string** must follow the OData standard for query representations - and is covered in [Query](#query) section. + and is covered in [Query](#query) section of OData specifications. While HTTP defines no constraints on how different resources are related -together, it does encourage the use of URL path segment hierarchies to convey relationships. In Microsoft Graph lifetime relationships between resources are -supported by the OData concepts of singletons, entitySets, entities, complex types and -navigation properties. +together, it does encourage the use of URL path segment hierarchies to convey +relationships. In Microsoft Graph lifetime relationships between resources are +supported by the OData concepts of singletons, entitySets, entities, complex +types and navigation properties. -#### Category +In Microsoft Graph a top-level API category may represent one of the following +groupings: -We define a **top-level API category** as a coherent area of API functionality -which covers one or multiple high-level use cases defined from customer and -enterprise perspectives and represents one of the following: ->> 1. A core *user-centric concept* of the Graph - - For example: /users, /groups or /me -1. A Microsoft *product or service offerings* covering multiple use cases - - For example: /teamwork, /directory -1. A *feature* offering covering a single use case and *shared* across multiple + + 1. For example: /users, /groups or /me + +2. A Microsoft *product or service offerings* covering multiple use cases + + 1. For example: /teamwork, /directory + +3. A *feature* offering covering a single use case and *shared* across multiple Microsoft products - - For example: /search, /notifications, /subscriptions, /files -1. *Administrative configuration* functions for specific products. (Note: this + + 1. For example: /search, /notifications, /subscriptions, /files + +4. *Administrative configuration* functions for specific products. (Note: this is not final and may be adjusted based on the survey results) - - For example: /admin/exchange -1. Internal Microsoft requirements for publishing Privileged and Hidden APIs, + + 1. For example: /admin/exchange + +5. Internal Microsoft requirements for publishing Privileged and Hidden APIs, routing, and load testing - - For example: /loadTestEntities ->> -Top-level API categories are aligned with documentation, developer tools, and in -general are relatively stable. If a new category needs to be created, it requires an API Review and an API Council approval. + + 1. For example: /loadTestEntities + +Effectively top-level categories define a perimeter for the API surface thus a +new category creation requires additional rigor and governance. + ### Query Support Microsoft Graph APIs should support basic query options in conformance with @@ -220,11 +232,9 @@ OData specifications and [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). |Requirements| |----------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **DO** support \$select, \$top, \$filter query options | -| :heavy_check_mark: **DO** support \$filter with eq, ne operations on properties of entities in the requested entity set | -| :heavy_check_mark: **DO** use batch request to avoid too long query options | -| :heavy_check_mark: **DO** use content-type text/plain for POST queries | -| :ballot_box_with_check: **CONSIDER** supporting \$skip, \$count | +| :heavy_check_mark: **SHOULD** support \$select on resource to enable properties projection | +| :heavy_check_mark: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| :heavy_check_mark: +| :ballot_box_with_check: **SHOULD** support pagination 4top and $count for collections | Limitations of \$query requests made to Microsoft Graph: @@ -237,20 +247,19 @@ Limitations of \$query requests made to Microsoft Graph: The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or -processing the request. One way to avoid this is to use the POST verb instead of GET, and pass the -query options part of the URL in the request body as described in the chapter -[OData Query +processing the request. One way to avoid this is to use the POST verb instead of +GET, and pass the query options part of the URL in the request body as described +in the chapter [OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). -| Additional Microsoft Graph rules for modeling resources| -|-------------------------------------------------------------------------------------| -| :heavy_check_mark: **DO** verify that the primary id of an entity type is string | -| :heavy_check_mark: **DO** verify that the primary key must also be defined as a property. | -| :heavy_check_mark: **DO** verify that the primary key is composed of a single property and not multiple. | -| :heavy_check_mark: **DO** use an object as the root of all JSON payloads. | -| :heavy_check_mark: **DO** use a value property in the root object to return a collection. | -| :heavy_check_mark: **DO** include @odata.type annotations when the type is ambiguous. | -| :no_entry: **DO NOT** add the property id to a complex type | +| Additional Microsoft Graph rules for modeling resources | +|----------------------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** use String type for ID | +| :heavy_check_mark: **SHOULD** use a primary key composed of a single property and not multiple. | +| :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | +| :heavy_check_mark: **MUST** use a value property in the root object to return a collection | +| :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | +| :no_entry: **SHOULD NOT** add the property id to a complex type | ### Recommended Modeling Patterns @@ -259,28 +268,29 @@ with multiple variants of common concept. Type Hierarchy, Facets, and Flat bag of properties are three most often used patterns in Microsoft Graph today: - Type hierarchy is represented by one abstract base type with a few common - properties and one sub-type for each variant - [Modelling with Subtypes Pattern](./Modelling%20with%20Subtypes%20Pattern.md) + properties and one sub-type for each variant [Modelling with Subtypes + Pattern](./Modelling%20with%20Subtypes%20Pattern.md) - Facets are represented by a single entity type with common properties and one facet property (of complex type) per variant. The facet properties only - have a value when the object represents that variant - [Modelling with Facets Pattern](./Modelling%20with%20Facets%20Pattern.md) + have a value when the object represents that variant [Modelling with Facets + Pattern](./Modelling%20with%20Facets%20Pattern.md) - Flat bag of properties is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required/meaningful for the variant given - by the type property. [Modelling with Flat Bag Pattern](./Modelling%20with%20Flat%20Bag%20Pattern.md) + by the type property. [Modelling with Flat Bag + Pattern](./Modelling%20with%20Flat%20Bag%20Pattern.md) -The following table shows summary of main qualities for each pattern -and will help to select a pattern preferred for your use case. +The following table shows summary of main qualities for each pattern and will +help to select a pattern preferred for your use case. -| API qualities Patterns | Properties and behavior described in metadata | Suited for strongly typed languages | Simple query construction | Syntactical backward compatible | -|---------------------------|------------------------------------------------|-------------------------------------|---------------------------|---------------------------------| -| Type hierarchy | yes | yes | no | yes | -| Facets | ok | ok | yes | yes | -| Flat bag | no | no | yes | yes | +| API qualities Patterns | Properties and behavior described in metadata | Suited for strongly typed languages | Simple query construction | Syntactical backward compatible | +|------------------------|-----------------------------------------------|-------------------------------------|---------------------------|---------------------------------| +| Type hierarchy | yes | yes | no | yes | +| Facets | ok | ok | yes | yes | +| Flat bag | no | no | yes | yes | ## Behavior Modeling @@ -291,19 +301,19 @@ response pattern should be one of the first implementation decisions you make. #### Microsoft Graph rules for modeling behavior -|Requirements |Severity| -|----------------------------------------------|----------------------------------------------| -| :heavy_check_mark: **DO** use POST to create new entities in insertable entity sets | Error | -| :heavy_check_mark: **DO** use PATCH to edit updatable resources | Error | -| :heavy_check_mark: **DO** use DELETE to delete deletable resources | Error | -| :heavy_check_mark: **DO** return a Location header with the edit URL or read URL of a created resource | Error | -| :heavy_check_mark: **DO** use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | Error | -| :heavy_check_mark: **DO** use POST …/{collection} for creating resources. | Error | -| :heavy_check_mark: **DO** use PATCH …/{collection}/{id} for updating resources. | Error | -| :no_entry: **DO NOT** use PUT …/{collection}/{id} for updating resources. | Warning | -| :no_entry: **DO NOT** use PATCH to replaces resources or PUT to partially update resources. | Error | -| :no_entry: **DO NOT** use patterns that require multiple round trips to complete a single logical action. | Warning | -| :ballot_box_with_check: **CONSIDER** supporting return and omit-nulls preferences. | Warning | +| Requirements | Severity | +|-----------------------------------------------------------------------------------------------------------------|----------| +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets | Error | +| :heavy_check_mark: **MUST** use PATCH to edit updatable resources | Error | +| :heavy_check_mark: **MUST** use DELETE to delete deletable resources | Error | +| :heavy_check_mark: **MUST** return a Location header with the edit URL or read URL of a created resource | Error | +| :heavy_check_mark: **MUST** use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | Error | +| :heavy_check_mark: **MUST** use POST …/{collection} for creating resources. | Error | +| :heavy_check_mark: **MUST** use PATCH …/{collection}/{id} for updating resources. | Error | +| :no_entry: **SHOULD NOT** use PUT …/{collection}/{id} for updating resources. | Warning | +| :no_entry: **MUST NOT** use PATCH to replaces resources or PUT to partially update resources. | Error | +| :no_entry: **SHOULD NOT** use patterns that require multiple round trips to complete a single logical action. | Warning | +| :ballot_box_with_check: **MAY** supporting return and omit-nulls preferences. | Warning | For a complete list of standard HTTP operations you can refer to the [Microsoft REST API @@ -316,7 +326,8 @@ follow when returning error condition responses. However, the structure, form and content of the error response payloads is currently not enforced leading to undiscoverable and inconsistent error messages. You can improve API traceability and consistency by using recommended Graph error model: -``` + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { "error": { "code": "BadRequest", @@ -336,12 +347,15 @@ and consistency by using recommended Graph error model: } } } -``` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + The following examples demonstrate error modeling for common use cases: - **Simple error**: A workload wants to report an error with top-level details - only. Then the error object contains the top-level error code, message and target (optional). -``` + only. Then the error object contains the top-level error code, message and + target (optional). + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { "error": { "code": "badRequest", @@ -349,15 +363,15 @@ The following examples demonstrate error modeling for common use cases: "target": "Service X (Optional)" } } -``` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- **Detailed error**: An API needs to provide service-specific details of the error via the innererror property of the - error object. The code property in innererror is optional but highly - recommended. It is intended to allow services to supply a specific - error code to help differentiate errors that share the same top-level error - code but reported for different reasons. +- **Detailed error**: An API needs to provide service-specific details of the + error via the innererror property of the error object. The code property in + innererror is optional but highly recommended. It is intended to allow + services to supply a specific error code to help differentiate errors that + share the same top-level error code but reported for different reasons. -``` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { "error": { "code": "badRequest", @@ -369,13 +383,14 @@ The following examples demonstrate error modeling for common use cases: } } } -``` -| Microsoft Graph enforces the following error rules|Severity| -|---------------------------------------------------------------------------------------------|---------| -| :heavy_check_mark: **DO** return an error property with a child code property in all error responses. | Error | -| :heavy_check_mark: **DO** return a 403 Forbidden error when insufficient scopes are present on the auth token. | Error | -| :heavy_check_mark: **DO** return a 429 Too many requests error when the caller has exceeded throttling limits. | Error | -| :ballot_box_with_check: **CONSIDER** returning a 404 Not found error if a 403 would result in information disclosure. | Warning | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +| Microsoft Graph enforces the following error rules | Severity | +|-----------------------------------------------------------------------------------------------------------------------|----------| +| :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | Error | +| :heavy_check_mark: **MUST** return a 403 Forbidden error when insufficient scopes are present on the auth token. | Error | +| :heavy_check_mark: **MUST** return a 429 Too many requests error when the caller has exceeded throttling limits. | Error | +| :ballot_box_with_check: **MAY** returning a 404 Not found error if a 403 would result in information disclosure. | Warning | For a complete mapping of error codes to HTTP statuses you can refer to the [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). @@ -389,23 +404,40 @@ In general, making changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a breaking change. -** Non-breaking changes:** -* Addition of an annotation OpenType="true" Addition of properties that are nullable or have a default value -* Addition of a member to an evolvable enumeration 1. Removal, rename, or change to the type of an open extension -* Removal, rename, or change to the type of an annotation *Introduction of paging to existing collections -* Changes to error codes Changes to the order of properties -* Changes to the length or format of opaque strings, such as resource IDs +\*\* Non-breaking changes:\*\* + +- Addition of an annotation OpenType="true" Addition of properties that are + nullable or have a default value + +- Addition of a member to an evolvable enumeration 1. Removal, rename, or + change to the type of an open extension + +- Removal, rename, or change to the type of an annotation \*Introduction of + paging to existing collections + +- Changes to error codes Changes to the order of properties + +- Changes to the length or format of opaque strings, such as resource IDs + +\*\* Breaking changes:\*\* + +- Changes to the URL or fundamental request/response associated with a + resource -** Breaking changes:** -* Changes to the URL or fundamental request/response associated with a resource -* Changing semantics of resource representation -* Removal, rename, or change to the type of a declared property -* Removal or rename of APIs or API parameters Addition of a required request header -* Addition of a EnumType members for non-extensible enumerations -* Addition of a Nullable="false" properties to existing types -* Addition of a Nullable="false" parameters to existing actions and functions -* Adding attributes to existing nodes is considered breaking. +- Changing semantics of resource representation +- Removal, rename, or change to the type of a declared property + +- Removal or rename of APIs or API parameters Addition of a required request + header + +- Addition of a EnumType members for non-extensible enumerations + +- Addition of a Nullable="false" properties to existing types + +- Addition of a Nullable="false" parameters to existing actions and functions + +- Adding attributes to existing nodes is considered breaking. For the full list of rules you can refer to [this section of the OData V4 spec](https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209). @@ -466,7 +498,8 @@ to annotate any usage of that type in entity sets, singletons, properties, navigation properties, functions, or actions. **Example of property annotation:** -``` + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -481,7 +514,8 @@ navigation properties, functions, or actions. ... -``` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + When the request URL contains a reference to a deprecated model element, the HTTP response includes a [Deprecation header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the @@ -490,12 +524,13 @@ years beyond the Deprecation date). Response also includes a link header pointing to the breaking changes page. **Deprecation header example:** -``` + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Deprecation: Thursday, 30 June 2022 11:59:59 GMT Sunset: Wed, 30 Mar 2022 23:59:59 GMT Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2020-06-30_state ; rel="deprecation"; type="text/html"; title="state" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``` **Deprecation cadence:** - As an API developer you can mark individual API schema elements as @@ -517,30 +552,30 @@ Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="de ## Recommended API Patterns -The guidelines in previous sections are intentionally brief and provide a -jump start for Graph API developers. More detailed design guidance on REST APIs is +The guidelines in previous sections are intentionally brief and provide a jump +start for Graph API developers. More detailed design guidance on REST APIs is published at the [Microsoft REST API -Guidelines](https://github.com/microsoft/api-guidelines/) and Graph specific patterns are -outlined in the table below. +Guidelines](https://github.com/microsoft/api-guidelines/) and Graph specific +patterns are outlined in the table below. Recommended API Design patterns: -| Pattern | Description | Reference | -|-------------------------|-------------|----------------------------------------------------------------------------------------------------------| -| Key Property |The ability to uniquely identify an object through the key | [Key Property](./evolvable-enums.md) | -| Entity Type | | | -| Complex Type | | | -| Shared Type | The ability to reuse a type defined by another service. | | -| Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Modeling with Subtypes](./Modelling%20with%20Subtypes%20Pattern.md) | -| Dictionary | The ability to persist a variable number of properties. | -| Evolvable Enums | The ability to enable non-breaking changes for Enum type. | | -| Type Namespace | The ability to reduce the need to prefix types with a qualifier to ensure uniqueness. | -| Change Tracking | The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph | -| Long Running Operations |The ability to model asynchronous operations. | | -| Delta Queries | The ability to query changes in the data exposed by Microsoft Graph | | -|Navigation Properties | | -|Viewpoint | | - +| Pattern | Description | Reference | +|-------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| +| Key Property | The ability to uniquely identify an object through the key | [Key Property](./evolvable-enums.md) | +| Entity Type | | | +| Complex Type | | | +| Shared Type | The ability to reuse a type defined by another service. | | +| Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Modeling with Subtypes](./Modelling%20with%20Subtypes%20Pattern.md) | +| Dictionary | The ability to persist a variable number of properties. | | +| Evolvable Enums | The ability to enable non-breaking changes for Enum type. | | +| Type Namespace | The ability to reduce the need to prefix types with a qualifier to ensure uniqueness. | | +| Change Tracking | The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph | | +| Long Running Operations | The ability to model asynchronous operations. | | +| Delta Queries | The ability to query changes in the data exposed by Microsoft Graph | | +| Navigation Properties | | | +| Viewpoint | | | +|Property projection $select|| ## References From 31e1b321bdd6f3778d76edf310615202e94c1729 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 1 Dec 2021 23:10:17 -0500 Subject: [PATCH 199/729] icon updates --- graph/GuidelinesGraph.md | 68 +++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 65d4a604..f8cf857b 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -76,16 +76,18 @@ a change or propose a new idea. This document offers prescriptive guidance labeled as follows: -:heavy_check_mark: **DO** satisfy this specification. If not following this +:heavy_check_mark: **MUST** satisfy this specification. If not following this advice, you MUST disclose your reason during the Graph API review. -:no_entry: **DO NOT** use this pattern. If not following this advice, you MUST +:no_entry: **MUST NOT** use this pattern. If not following this advice, you MUST disclose your reason during the Graph API review. -:ballot_box_with_check: **YOU SHOULD** fulfill this specification. If not +:ballot_box_with_check: **SHOULD** fulfill this specification. If not following this advice, you MUST disclose your reason during the Graph API review. +:warning: **SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Graph API review. + ## Design Approach The design of your API is arguably the most important investment you will make @@ -120,6 +122,8 @@ resources, their properties, and relationships and further refer to it as entity data model. There is no one-to-one correspondence between domain model elements and API resources as APIs usually support only customer-facing use cases. +[Domain model example](DomainModel.png) + After API resources are identified you need to name them and their properties so that the API will be discoverable and intuitive for developers, and consistent with other Graph resources. @@ -147,23 +151,23 @@ Below is a short summary of the most often used conventions. | Requirements | Example | |---------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | :no_entry: **MUST NOT** use redundant words in names. |- **Right:** /places/{id}/**type** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | -| :no_entry: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | -| :no_entry: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | +| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | +| :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | | :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong** addresses | | :heavy_check_mark: **MUST** use plural nouns for collections (for listing a type or collection properties). | - **Right:** addresses
    - **Wrong** address | -| :heavy_check_mark: **SHOULD** pluralize the noun even when followed by an adjective (a "postpositive"). | - **Right:** passersby or mothersInLaw
    - **Wrong** notaryPublics or motherInLaws | +| :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a "postpositive"). | - **Right:** passersby or mothersInLaw
    - **Wrong** notaryPublics or motherInLaws | | **casing** | | | :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces | - **Right:** automaticRepliesStatus.
    - **Wrong** kebab-case or snake_case. | -| :heavy_check_mark: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | -| :heavy_check_mark: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | +| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | +| :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | | :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate or crosswalk
    - **Wrong** metaData, semiCircle or airPlane | | :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw or postOffice
    - **Wrong** paperclip or fullmoon | | **prefixes and suffixes** | | | :heavy_check_mark: **MUST** suffix date and time properties with | - **Right:** dueDate — an Edm.Date
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Wrong** dueOn or startTime -
    - **Right:** instead both above are an Edm.DateTimeOffset | -| :heavy_check_mark: **SHOULD** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32 | +| :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32 | | :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong** enabledBool | -| :heavy_check_mark: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl | -| :heavy_check_mark: **SHOULD** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong** enabled or allowResourcAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | +| :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl | +| :ballot_box_with_check: **SHOULD** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong** enabled or allowResourcAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | | :no_entry: **MUST NOT** use 'collection', 'response', 'request ' suffixes . |- **Right:** addresses
    - **Wrong** addressCollection | ### Uniform Resource Locators (URLs) @@ -232,8 +236,8 @@ OData specifications and [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). |Requirements| |----------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **SHOULD** support \$select on resource to enable properties projection | -| :heavy_check_mark: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| :heavy_check_mark: +| :ballot_box_with_check: **SHOULD** support \$select on resource to enable properties projection | +|:ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| :heavy_check_mark: | :ballot_box_with_check: **SHOULD** support pagination 4top and $count for collections | Limitations of \$query requests made to Microsoft Graph: @@ -255,11 +259,11 @@ Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conv | Additional Microsoft Graph rules for modeling resources | |----------------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** use String type for ID | -| :heavy_check_mark: **SHOULD** use a primary key composed of a single property and not multiple. | +| :ballot_box_with_check: **SHOULD** use a primary key composed of a single property and not multiple. | | :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | | :heavy_check_mark: **MUST** use a value property in the root object to return a collection | | :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | -| :no_entry: **SHOULD NOT** add the property id to a complex type | +| :warning: **SHOULD NOT** add the property id to a complex type | ### Recommended Modeling Patterns @@ -286,10 +290,10 @@ of properties are three most often used patterns in Microsoft Graph today: The following table shows summary of main qualities for each pattern and will help to select a pattern preferred for your use case. -| API qualities Patterns | Properties and behavior described in metadata | Suited for strongly typed languages | Simple query construction | Syntactical backward compatible | +| API qualities Patterns | Properties and behavior described in metadata | Suited for multiple inheritance | Simple query construction | Syntactical backward compatible | |------------------------|-----------------------------------------------|-------------------------------------|---------------------------|---------------------------------| -| Type hierarchy | yes | yes | no | yes | -| Facets | ok | ok | yes | yes | +| Type hierarchy | yes | no | no | yes | +| Facets | ok | yes | yes | yes | | Flat bag | no | no | yes | yes | ## Behavior Modeling @@ -310,9 +314,9 @@ response pattern should be one of the first implementation decisions you make. | :heavy_check_mark: **MUST** use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | Error | | :heavy_check_mark: **MUST** use POST …/{collection} for creating resources. | Error | | :heavy_check_mark: **MUST** use PATCH …/{collection}/{id} for updating resources. | Error | -| :no_entry: **SHOULD NOT** use PUT …/{collection}/{id} for updating resources. | Warning | +| :warning: **SHOULD NOT** use PUT …/{collection}/{id} for updating resources. | Warning | | :no_entry: **MUST NOT** use PATCH to replaces resources or PUT to partially update resources. | Error | -| :no_entry: **SHOULD NOT** use patterns that require multiple round trips to complete a single logical action. | Warning | +| :warning: **SHOULD NOT** use patterns that require multiple round trips to complete a single logical action. | Warning | | :ballot_box_with_check: **MAY** supporting return and omit-nulls preferences. | Warning | For a complete list of standard HTTP operations you can refer to the [Microsoft @@ -563,19 +567,19 @@ Recommended API Design patterns: | Pattern | Description | Reference | |-------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| | Key Property | The ability to uniquely identify an object through the key | [Key Property](./evolvable-enums.md) | -| Entity Type | | | -| Complex Type | | | -| Shared Type | The ability to reuse a type defined by another service. | | +| Entity Type | | TBD | +| Complex Type | | TBD | +| Shared Type | The ability to reuse a type defined by another service. | TBD | TBD | | Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Modeling with Subtypes](./Modelling%20with%20Subtypes%20Pattern.md) | -| Dictionary | The ability to persist a variable number of properties. | | -| Evolvable Enums | The ability to enable non-breaking changes for Enum type. | | -| Type Namespace | The ability to reduce the need to prefix types with a qualifier to ensure uniqueness. | | -| Change Tracking | The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph | | -| Long Running Operations | The ability to model asynchronous operations. | | -| Delta Queries | The ability to query changes in the data exposed by Microsoft Graph | | -| Navigation Properties | | | -| Viewpoint | | | -|Property projection $select|| +| Dictionary | The ability to persist a variable number of properties. | TBD | +| Evolvable Enums | The ability to enable non-breaking changes for Enum type. | TBD | +| Type Namespace | The ability to reduce the need to prefix types with a qualifier to ensure uniqueness. | TBD | +| Change Tracking | The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph | TBD | +| Long Running Operations | The ability to model asynchronous operations. | TBD | +| Delta Queries | The ability to query changes in the data exposed by Microsoft Graph | TBD | +| Navigation Properties | | TBD | +| Viewpoint | | TBD | +|Property projection $select| |TBD ## References From e50655812f04bc5e132a36fe67185d8bdddc1e20 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 1 Dec 2021 23:11:15 -0500 Subject: [PATCH 200/729] domain model image --- graph/DomainModel.png | Bin 0 -> 150674 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 graph/DomainModel.png diff --git a/graph/DomainModel.png b/graph/DomainModel.png new file mode 100644 index 0000000000000000000000000000000000000000..869206bfc8cbd2ae739b84ba605c22de25fc77a0 GIT binary patch literal 150674 zcmY(qbyyqGw>6BkxI>FuDPG*&S}0a1TAboqyjalUPH_!V+}(q_wODY6;1(=M@^SBb zfA{^~=b6l8^3Tl4*=L`%*IGML{fh!F76ld(5)!WB=Z_jlNN6u#QB5&WUM`l>{Jk$1 zWH${3S)}SIs>7F?SJpDBGDt{u@z_t_U%%XAI)B!4Lqfvq|E~}7uv4i85)z_F@uQ5E zm+?v0>rQQj)N|mS)w+1b9h~90czC(kaoN~&+Fi6t(YfQU0J-2}L^uX=uvo8v@JGz| z1GpH~m}maat!w5hP37>4@+k=_PNLt=#Ge;hbfcQ=!PQ3EzQ9LVrHSr5D2#q2G(VXL zuW$mH))nItLx8nGV%s;_dxCM5irZM8tkC)in&B&Hud&ZZQOL>IiL5d=nin)tIL9m= zez7lSy(<+PcR;FUiOxk`sxx=~FhKY5`-H{BI}mP@cii|nCd9ACbijJx83p$Tb1W~= zd53|X7U(6d6)vb*uF*$ncrden+GGUjV&o^S? z*A(Yp+TOmGmy011V4crj`n#_-|E#QlljtQvflifeHkR|dSEOYRxY#jmez6)oGW6LL zKt-BcsL)nz?PmKMU$-F!|9=NUiuqpydAF}Xz+^pf55kT!$A12WjrEDaY)ZLT>1w*Z zAkghAdsX>pS+GD^vrX=>0V@ewnXt2m+|Y%%^5!F;8hsaodY3QkE|x8fi#hUhm!b}K z%cW^{>vlh42XQfUachV{|Irz1I4#COWh;+_$EV;pG?7FNn{#tA(a(CqLxS@spqcRg zW8NF_y(ZE|8Xh8m^ZiJK3eSfbkord`me8A6j?nl_GU6W^U9tqPWd8eTTb4n_Xu}94 z`!>)YBmKnLXLZSyrXPZ#VP@7I(l`{Vrm8wd1<~ldAgWSq`jm7^QrG1k7(Bqh;7xcj zUMB5YG+mO5c*pq5Y>aY8#2HB^S){d*-nE(^>-2qRnK)rR!F}j#8mb8I>K4N|{**lp z_!7jgWS|iurJR2}N?OnIES>BApLda|HdaTH<$RThmRO4sW>jYyRfBM)ny&vaEkw
    @8>it6|C9nrnXAd}ho8N56 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
    f|vDE#rc zk7;_&10x?XZqSK`Z;!%lha zb%+kYCM&YS!dbgYxf5y<&m`C_e>2aX!?~WIiaMX>HqAFJi Date: Wed, 1 Dec 2021 23:18:45 -0500 Subject: [PATCH 201/729] whiteboard picture --- graph/GuidelinesGraph.md | 2 +- graph/ResourceModel.png | Bin 0 -> 202679 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 graph/ResourceModel.png diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index f8cf857b..be3464a6 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -122,7 +122,7 @@ resources, their properties, and relationships and further refer to it as entity data model. There is no one-to-one correspondence between domain model elements and API resources as APIs usually support only customer-facing use cases. -[Domain model example](DomainModel.png) +![Domain model example](ResourceModel.png) After API resources are identified you need to name them and their properties so that the API will be discoverable and intuitive for developers, and consistent diff --git a/graph/ResourceModel.png b/graph/ResourceModel.png new file mode 100644 index 0000000000000000000000000000000000000000..c7a964406a353ca0af03fb08b572756a23b17143 GIT binary patch literal 202679 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%0DIa@e3CX+WU`D*1{ogccobN`n( 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 literal 0 HcmV?d00001 From 9792750a411c16c6ff3b07425542fe15b57cb875 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 1 Dec 2021 23:23:30 -0500 Subject: [PATCH 202/729] cropped picture --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index be3464a6..7b41d402 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -120,7 +120,7 @@ To create a good API you need to start with understanding your **use cases** and supporting domain model. We describe domain models in terms of entities or resources, their properties, and relationships and further refer to it as entity data model. There is no one-to-one correspondence between domain model elements -and API resources as APIs usually support only customer-facing use cases. +and API resources as APIs usually support only customer-facing use cases. A simple resource diagram makes it easier to reason about resource relationships and a shape of your API. ![Domain model example](ResourceModel.png) From 237d3a6c667b1a4ab30293bba7c21b6bde595a97 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 1 Dec 2021 23:23:59 -0500 Subject: [PATCH 203/729] cropped picture --- graph/ResourceModel.png | Bin 202679 -> 196338 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/graph/ResourceModel.png b/graph/ResourceModel.png index c7a964406a353ca0af03fb08b572756a23b17143..70e4ec7c2e56b89c09d2756ede2ad0f1411c4c19 100644 GIT binary patch literal 196338 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 z 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 literal 202679 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%0DIa@e3CX+WU`D*1{ogccobN`n( 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 From e8a00501752f759c317bf1cbf1835cb244d934ff Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 2 Dec 2021 17:15:54 -0500 Subject: [PATCH 204/729] generic context --- graph/GuidelinesGraph.md | 2 +- graph/Modelling with Subtypes Pattern.md | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 7b41d402..df4ea7de 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -167,7 +167,7 @@ Below is a short summary of the most often used conventions. | :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32 | | :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong** enabledBool | | :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl | -| :ballot_box_with_check: **SHOULD** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong** enabled or allowResourcAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | +| :ballot_box_with_check: **SHOULD** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | | :no_entry: **MUST NOT** use 'collection', 'response', 'request ' suffixes . |- **Right:** addresses
    - **Wrong** addressCollection | ### Uniform Resource Locators (URLs) diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index 1991ecf9..c7dcc204 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -10,7 +10,9 @@ Microsoft Graph API Design Pattern ## Context -Let’s assume you need to model an API to manage groups in an +Often an API needs to serve business entities which come in slightly different variation. These entities can be represented as a type hierarchy similar to the OOP inheritance concept. The hierarchy will be modeled as a base type and multiple subtypes which represent variations in metadata and behavior. In this model common elements of the base type can be reused, extended, or modified by subtypes. + +For example let’s assume you need to model an API to manage groups in an organization, where employees can create groups and become owners of the group by default. At the same time to support business processes some groups may be created automatically by daemon applications using a @@ -24,13 +26,12 @@ description. ## Problem + How to design an API to work with a collection of heterogeneous elements that have a set of common properties and behaviors, and some unique properties for each variant. Furthermore, it should be straightforward to add new variants to the API as needed in the future. + -How to model group owners to allow a heterogeneous collection of users -and principles so that API design can reflect common and relevant -properties for each subset of owners and accommodate new owner types in -future? +Or as in previous example how to model group owners to allow a heterogeneous collection of users +and principles and be able to accommodate new owner types in future? -A more general problem is how to model a collection of heterogeneous elements that have a set of common properties and behaviors, and some unique properties for each variant. Furthermore, it should be straightforward to add new variants to the API as needed in the future. ## Solution -------- From 4f9ed77da565115f02b38a40f5d6cc5a56e025e9 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 5 Dec 2021 19:45:52 -0500 Subject: [PATCH 205/729] First go at converting dictionary guidance to Pattern template --- .../dictionary/client-guidance.md | 0 graph/{ => patterns}/dictionary/index.md | 57 ++++++++++++++----- 2 files changed, 44 insertions(+), 13 deletions(-) rename graph/{ => patterns}/dictionary/client-guidance.md (100%) rename graph/{ => patterns}/dictionary/index.md (68%) diff --git a/graph/dictionary/client-guidance.md b/graph/patterns/dictionary/client-guidance.md similarity index 100% rename from graph/dictionary/client-guidance.md rename to graph/patterns/dictionary/client-guidance.md diff --git a/graph/dictionary/index.md b/graph/patterns/dictionary/index.md similarity index 68% rename from graph/dictionary/index.md rename to graph/patterns/dictionary/index.md index fb2fdce7..2047a015 100644 --- a/graph/dictionary/index.md +++ b/graph/patterns/dictionary/index.md @@ -1,12 +1,40 @@ -# Dictionary types +# Dictionary + +Microsoft Graph API Design Pattern + +*The Dictionary type provides the ability to create a set of primitives or objects, of the same type, where the API consumer can define a name for each value in the set.* + +## Problem +-------- + +The API design requires a resource to include an unknown quantity of similar data elements that need to be named using values provided by the API consumer. + +## Solution +-------- + +API designers use a JSON object to represent a dictionary in a `application/json`response payload. When describing the model in CSDL, a new complex type can be created that derives from `Org.OData.Core.V1.Dictionary` and then use the `Org.OData.Validation.V1.OpenPropertyTypeConstraint`to constrain the type that can be used for the values in the dictionary. + +Dictionary entries can be added via `POST`, updated via `PATCH`, and they can be removed by setting the entry value to `null`. Multiple dictionaries can be updated at once by using `PATCH` on the dictionary property. + +## Issues and Considerations +------------------------- Dictionaries, sometimes called maps, are a collection of name-value pairs. They allow dynamic data sets to be accessed in a systematic manner and are a good compromise between a strictly defined ahead of time structure with all its named properties and between a loosely defined dynamic object (i.e. OData OpenTypes). +As dictionary entries are removed via setting the value to null, this means that dictionaries can only support values that are non-nullable. + + OpenQuestions: + - Can/should PUT be supported on the dictionary property and/or the entry value + - What does OData say about being able to POST to a structured property? Will OData Web API allow that? + - Must an implementer support PATCH at both the dictionary level and the entry level? + More information: - [OData reference](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md#dictionary) -## When to use dictionary types +  +## When to Use this Pattern +------------------------ Before using a dictionary type in your API definition make sure your scenario fits the following criteria: @@ -20,7 +48,10 @@ Before using a dictionary type in your API definition make sure your scenario fi - [Open types](https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/use-open-types-in-odata-v4) when your data is not a collection in nature. - [Complex types](https://docs.microsoft.com/en-us/odata/webapi/complextypewithnavigationproperty) when the set of data values are known. -## JSON payload example +## Examples +------- + +### JSON payload example The following example illustrates the resulting JSON for a property of dictionary type. The parent object has been ommitted for brievety. @@ -38,11 +69,11 @@ The following example illustrates the resulting JSON for a property of dictionar } ``` -## HTTP calls examples +### HTTP calls examples In this set of examples we're modeling a **roles** property of dictionary type on the user entity which is exposed by the users entity set. -### Getting an entry from the dictionary +#### Getting an entry from the dictionary ```HTTP GET https://graph.microsoft.com/v1.0/users/10/roles/author @@ -56,7 +87,7 @@ Reponse: } ``` -### Getting the dictionary +#### Getting the dictionary ```HTTP GET https://graph.microsoft.com/v1.0/users/10/roles @@ -78,7 +109,7 @@ Reponse: } ``` -### Getting the entity with the dictionary +#### Getting the entity with the dictionary ```HTTP GET https://graph.microsoft.com/v1.0/users/10 @@ -104,7 +135,7 @@ Reponse: } ``` -### Creating an entry in the dictionary +#### Creating an entry in the dictionary ```HTTP POST https://graph.microsoft.com/v1.0/users/10/roles/author @@ -114,7 +145,7 @@ POST https://graph.microsoft.com/v1.0/users/10/roles/author } ``` -### Updating the dictionary +#### Updating the dictionary ```HTTP PATCH https://graph.microsoft.com/v1.0/users/10/roles @@ -137,7 +168,7 @@ PATCH https://graph.microsoft.com/v1.0/users/10/roles > Note: the domain values for the existing author and maintainer entries will get udpated. > Note: the reviewer entry will be inserted in the dictionary. -### Updating an entry in the dictionary +#### Updating an entry in the dictionary ```HTTP PATCH https://graph.microsoft.com/v1.0/users/10/roles/author @@ -147,13 +178,13 @@ PATCH https://graph.microsoft.com/v1.0/users/10/roles/author } ``` -### Deleting an entry from the dictionary +#### Deleting an entry from the dictionary ```HTTP DELETE https://graph.microsoft.com/v1.0/users/10/roles/author ``` -## CDSL example +### CDSL example The following example defines a complex type **roleSettings** as well as a dictionary of which the key will be a string and the value a **roleSettings**. @@ -182,6 +213,6 @@ The following example defines a complex type **roleSettings** as well as a dicti ``` -## Additional information +### Additional information [SDK implementation guidance](./client-guidance.md). From ad969231a6bad8c37d74c7ccc4520f78628d098e Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 5 Dec 2021 20:26:31 -0500 Subject: [PATCH 206/729] Fixed language --- graph/patterns/dictionary/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/dictionary/index.md b/graph/patterns/dictionary/index.md index 2047a015..75557ae4 100644 --- a/graph/patterns/dictionary/index.md +++ b/graph/patterns/dictionary/index.md @@ -7,7 +7,7 @@ Microsoft Graph API Design Pattern ## Problem -------- -The API design requires a resource to include an unknown quantity of similar data elements that need to be named using values provided by the API consumer. +The API design requires a resource to include an unknown quantity of data elements of the same type that must be named using values provided by the API consumer. ## Solution -------- From c4a63c94bd20b80af2a480890b9304085a875715 Mon Sep 17 00:00:00 2001 From: Darrel Date: Mon, 6 Dec 2021 19:25:02 -0500 Subject: [PATCH 207/729] Update graph/patterns/dictionary/index.md --- graph/patterns/dictionary/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/dictionary/index.md b/graph/patterns/dictionary/index.md index 75557ae4..f84cbccd 100644 --- a/graph/patterns/dictionary/index.md +++ b/graph/patterns/dictionary/index.md @@ -14,7 +14,7 @@ The API design requires a resource to include an unknown quantity of data elemen API designers use a JSON object to represent a dictionary in a `application/json`response payload. When describing the model in CSDL, a new complex type can be created that derives from `Org.OData.Core.V1.Dictionary` and then use the `Org.OData.Validation.V1.OpenPropertyTypeConstraint`to constrain the type that can be used for the values in the dictionary. -Dictionary entries can be added via `POST`, updated via `PATCH`, and they can be removed by setting the entry value to `null`. Multiple dictionaries can be updated at once by using `PATCH` on the dictionary property. +Dictionary entries can be added via `POST`, updated via `PATCH`, and they can be removed by setting the entry value to `null`. Multiple entries can be updated at once by using `PATCH` on the dictionary property. ## Issues and Considerations ------------------------- From 5b9708248bf83021dcc4934a6c390b5589f35798 Mon Sep 17 00:00:00 2001 From: tg-msft Date: Fri, 10 Dec 2021 09:06:29 -0800 Subject: [PATCH 208/729] Update error guidance --- azure/ConsiderationsForServiceDesign.md | 27 +++++++++++++++++++++++++ azure/Guidelines.md | 18 +++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index be1562c6..b6cfc083 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -229,6 +229,33 @@ An important distinction between RELO and status monitor LROs is that there is a status monitor LRO, whereas the status of all RELO operations is combined into the status of the resource. So status monitor LROs are "one-to-one" with their operation status, whereas RELO-style LROs are "many-to-one". +## Errors +One of the most important parts of service design is also one of the most overlooked. The errors returned by your service are a critical part of your developer experience. Your service and your customer's application together form a distributed system. Errors are inevitable, but well designed errors enabling customers to self-diagnose and recover from faults won't take down their applications or incur overwhelming customer support costs for you. + +First, you should always try to design errors out of existence if possible. You'll get a lot of this for free by following the Guidelines. Some examples include: +- Idempotent APIs solve a whole class of network issues where customers have no idea how to proceed if they send a request to the service but never get a response. +- Accessing resources from multiple microservices can quickly lead to complex race conditions so conditional requests provide optimistic concurrency for safe usage. +- Reframing the purpose of an API can obviate some errors. This is most often specific to your operations, but an example from the Guidelines is thinking about `DELETE`s as _"ensure no resource at this location exists"_ so they can return an easier to use `204` instead of _"delete this exact resource instance"_ which would fail with a non-idempotent `404`. + +There are two types of errors returned from your service and customers handle them differently. +- Usage errors where the customer is calling your API incorrectly. The customer can easily make these errors go away by fixing their code. We expect most usage errors to be found during testing. +- Runtime errors that can't be prevented by the customer and need to be recovered from. Some runtime errors like `429` throttling will be handled automatically by client libraries, but most will be situations like a `409` conflict that requires knowledge about the customer's application to remedy. + +We provide appropriate [HTTP status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses) for customers to handle errors generically and error code strings in our common error schema and the `x-ms-error-code` header for customers to handle errors specifically. As an example, consider what a customer would do when trying to get the properties of a Storage blob: +- A `404` status code tells them the blob doesn't exist and the customer can report the error to their users +- A `BlobNotFound` or `ContainerNotFound` error code will tell them why the blob doesn't exist so they can take steps to recreate it + +The common error schema in the Guidelines allows nested details and inner errors that have their own error codes, but the top-level error code is the most important. The HTTP status code and the top-level error code are the only part of your error that we consider part of your API contract that follows the same compatibility requirements as the rest of your API. Importantly, this means you **can not change the HTTP status code or top-level error code for an API in GA'ed service version**. You can only return new status codes and error codes in future API versions if customers make use of new features that trigger new classes of errors. Battle tested error handling is some of the hardest code to get right and we can't break that for customers when they upgrade to the latest version. The rest of the properties in your error like `message`, `details`, etc., are not considered part of your API contract and can change to improve the diagnosability of your service. + +We also return the top-level error code as the `x-ms-error-code` header so client libraries have the ability to automatically retry requests when possible without having to parse a JSON payload. We recommend unique error codes like `ContainerBeingDeleted` for every distinct recoverable error that can occur, but suggest reusing common error codes like `InvalidHeaderValue` for usage errors where a descriptive error message is more important for resolving the problem. The Storage [Common](https://docs.microsoft.com/en-us/rest/api/storageservices/common-rest-api-error-codes) and [Blob](https://docs.microsoft.com/en-us/rest/api/storageservices/blob-service-error-codes) error codes are a good starting point if you're looking for examples. + +You should not use your OpenAPI/Swagger spec to document every error that can occur. The `"default"` response is the only thing AutoRest considers an error response unless you provide other annotations. Every unique status code turns into a separate code path in your client libraries so we do not encourage this practice. The only reason to document specific error codes is if they return a different error response than the default, but that is also heavily discouraged. You can [define an enum in your spec](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/storage/data-plane/Microsoft.BlobStorage/preview/2021-04-10/blob.json#L10419) with `"modelAsString": true` that lists all of the top-level error codes to make it [easier for your customers to handle specific error codes](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/storage/Azure.Storage.Blobs#troubleshooting). + +Be as precise as possible when writing error messages. A message with just `Invalid Argument` is almost useless to a customer who sent 100KB of JSON to your endpoint. ``Query parameter `top` must be less than or equal to 1000`` tells a customer exactly what went wrong so they can quickly fix the problem. Don't go overboard while writing great, understandable error messages and include any sensitive customer information or secrets though. Many developers will blindly write any error to logs that don't have the same level of access control as Azure resources. + + +Finally, write sample code for your service's workflow and add the code you'd want customers using to gracefully recover from errors. Is it actually graceful? Is it something you'd be comfortable asking most customers to write? We also highly encourage reaching out to customers during private preview and asking them for code they've written against your service. Their error handling might match your expectations, you might find a strong need for better documentation, or you might find important opportunities to improve the errors you're returning. + ## Getting Help: The Azure REST API Stewardship Board The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index d3b751dc..84891cc8 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -292,11 +292,15 @@ There are 2 kinds of errors: :white_check_mark: **DO** return an `x-ms-error-code` response header with a string error code indicating what went wrong. -*NOTE: Error code values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future.* +*NOTE: `x-ms-error-code` values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future.* -:white_check_mark: **DO** carefully craft `x-ms-error-code` string values for errors that are recoverable at runtime. +:heavy_check_mark: **YOU MAY** implement the `x-ms-error-code` values as an enum with `"modelAsString": true` because it's possible add new values over time. In particular, it's only a breaking change if the same conditions result in a different top-level error code. -:white_check_mark: **DO** ensure that the top-level `code` field's value is identical to the `x-ms-error-code` header's value. +:warning: **YOU SHOULD NOT** add new top-level error codes to an existing API without bumping the service version. + +:white_check_mark: **DO** carefully craft unique `x-ms-error-code` string values for errors that are recoverable at runtime. Reuse common error codes for usage errors that are not recoverable. + +:white_check_mark: **DO** ensure that the top-level error's `code` value is identical to the `x-ms-error-code` header's value. :white_check_mark: **DO** document the service's error code strings; they are part of the API contract. @@ -306,7 +310,7 @@ There are 2 kinds of errors: Property | Type | Required | Description -------- | ---- | :------: | ----------- -`error` | ErrorDetail | ✔ | The error object. +`error` | ErrorDetail | ✔ | The top-level error object whose `code` matches `x-ms-error-code` **ErrorDetail** : Object @@ -317,6 +321,7 @@ Property | Type | Required | Description `target` | String | | The target of the error. `details` | ErrorDetail[] | | An array of details about specific errors that led to this reported error. `innererror` | InnerError | | An object containing more specific information than the current object about the error. +_additional properties_ | | | Additional properties that can be useful when debugging. **InnerError** : Object @@ -344,6 +349,11 @@ Example: :heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are _not_ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. +:heavy_check_mark: **YOU MAY** add additional properties for any data values in your error message so customers don't resort to parsing your error message. For example, an error with `"message": "A maximum of 16 keys are allowed per account."` might also add a `"maximumKeys": 16` property. This is not part of your API contract and should only be used for diagnosing problems. + +*Note: Do not use this mechanism to provide information developers need to rely on in code (ex: the error message can give details about why you've been throttled, but the `Retry-After` should be what developers rely on to back off).* + +:warning: **YOU SHOULD NOT** use your OpenAPI/Swagger specification to document every failing status code or error code for each operation. ### JSON Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: From dfb35c4d8886edf560c53c6bdd50cce80bb1694c Mon Sep 17 00:00:00 2001 From: tg-msft Date: Fri, 10 Dec 2021 10:18:26 -0800 Subject: [PATCH 209/729] PR feedback --- azure/ConsiderationsForServiceDesign.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index b6cfc083..55b8adeb 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -230,7 +230,7 @@ status monitor LRO, whereas the status of all RELO operations is combined into t So status monitor LROs are "one-to-one" with their operation status, whereas RELO-style LROs are "many-to-one". ## Errors -One of the most important parts of service design is also one of the most overlooked. The errors returned by your service are a critical part of your developer experience. Your service and your customer's application together form a distributed system. Errors are inevitable, but well designed errors enabling customers to self-diagnose and recover from faults won't take down their applications or incur overwhelming customer support costs for you. +One of the most important parts of service design is also one of the most overlooked. The errors returned by your service are a critical part of your developer experience. Your service and your customer's application together form a distributed system. Errors are inevitable, but well-designed errors can help you avoid costly customer support incidents by empowering customers to self-diagnose problems. First, you should always try to design errors out of existence if possible. You'll get a lot of this for free by following the Guidelines. Some examples include: - Idempotent APIs solve a whole class of network issues where customers have no idea how to proceed if they send a request to the service but never get a response. @@ -239,7 +239,7 @@ First, you should always try to design errors out of existence if possible. You There are two types of errors returned from your service and customers handle them differently. - Usage errors where the customer is calling your API incorrectly. The customer can easily make these errors go away by fixing their code. We expect most usage errors to be found during testing. -- Runtime errors that can't be prevented by the customer and need to be recovered from. Some runtime errors like `429` throttling will be handled automatically by client libraries, but most will be situations like a `409` conflict that requires knowledge about the customer's application to remedy. +- Runtime errors that can't be prevented by the customer and need to be recovered from. Some runtime errors like `429` throttling will be handled automatically by client libraries, but most will be situations like a `409` conflict requiring knowledge about the customer's application to remedy. We provide appropriate [HTTP status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses) for customers to handle errors generically and error code strings in our common error schema and the `x-ms-error-code` header for customers to handle errors specifically. As an example, consider what a customer would do when trying to get the properties of a Storage blob: - A `404` status code tells them the blob doesn't exist and the customer can report the error to their users From d68ba1177cb9ea46a7e63919338ee6a9c194bb1b Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 13 Dec 2021 19:28:21 -0500 Subject: [PATCH 210/729] moved deprecation to standalone md --- graph/GuidelinesGraph.md | 223 +++++------------------ graph/ModelExample.png | Bin 0 -> 66461 bytes graph/Modelling with Subtypes Pattern.md | 32 ++-- graph/PatternDescriptionTemplate.md | 11 +- graph/deprecation.md | 81 ++++++++ 5 files changed, 151 insertions(+), 196 deletions(-) create mode 100644 graph/ModelExample.png create mode 100644 graph/deprecation.md diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index df4ea7de..496820e9 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -13,11 +13,10 @@ Table of Contents - [Query Support](#query-support) - [Recommended Modeling Patterns](#recommended-modeling-patterns) - [Behavior Modeling](#behavior-modeling) - - [Microsoft Graph rules for modeling behavior](#microsoft-graph-rules-for-modeling-behavior) + - [Microsoft Graph rules for modeling behavior COMBINE ACCORDINGLY](#microsoft-graph-rules-for-modeling-behavior-combine-accordingly) - [Error Handling](#error-handling) - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) - [Versioning and Deprecation](#versioning-and-deprecation) - - [Deprecation Process](#deprecation-process) - [Recommended API Patterns](#recommended-api-patterns) - [References](#references) @@ -122,7 +121,7 @@ resources, their properties, and relationships and further refer to it as entity data model. There is no one-to-one correspondence between domain model elements and API resources as APIs usually support only customer-facing use cases. A simple resource diagram makes it easier to reason about resource relationships and a shape of your API. -![Domain model example](ResourceModel.png) +![Resource model example](ModelExample.png) After API resources are identified you need to name them and their properties so that the API will be discoverable and intuitive for developers, and consistent @@ -203,28 +202,18 @@ types and navigation properties. In Microsoft Graph a top-level API category may represent one of the following groupings: -1. A core *user-centric concept* of the Graph +1. A core *user-centric concept* of the Graph, i.e. /users, /groups or /me. - 1. For example: /users, /groups or /me - -2. A Microsoft *product or service offerings* covering multiple use cases - - 1. For example: /teamwork, /directory +2. A Microsoft *product or service offerings* covering multiple use cases, i.e. /teamwork, /directory. 3. A *feature* offering covering a single use case and *shared* across multiple - Microsoft products - - 1. For example: /search, /notifications, /subscriptions, /files + Microsoft products, i.e. /search, /notifications, /subscriptions, /files. 4. *Administrative configuration* functions for specific products. (Note: this - is not final and may be adjusted based on the survey results) - - 1. For example: /admin/exchange + is not final and may be adjusted based on the survey results), i.e. /admin/exchange. 5. Internal Microsoft requirements for publishing Privileged and Hidden APIs, - routing, and load testing - - 1. For example: /loadTestEntities + routing, and load testing, i.e./loadTestEntities. Effectively top-level categories define a perimeter for the API surface thus a new category creation requires additional rigor and governance. @@ -289,12 +278,14 @@ of properties are three most often used patterns in Microsoft Graph today: The following table shows summary of main qualities for each pattern and will help to select a pattern preferred for your use case. +| Use case | | | | Recommended Pattern | + -| API qualities Patterns | Properties and behavior described in metadata | Suited for multiple inheritance | Simple query construction | Syntactical backward compatible | -|------------------------|-----------------------------------------------|-------------------------------------|---------------------------|---------------------------------| -| Type hierarchy | yes | no | no | yes | -| Facets | ok | yes | yes | yes | -| Flat bag | no | no | yes | yes | +| API qualities\ | Properties and behavior described in metadata | Suited for combinations of properties and behaviors | Simple query construction | Syntactical backward compatible | +|------------------------|-----------------------------------------------|-------------------------------------|---------------------------|---------------------------------|---------------------------| +| Use Case 1 | yes | no | no | yes |Type hierarchy | +| Use Case 2 | ok | yes | yes | yes |Facets | +| Use Case 3 | no | no | yes | yes |Flat bag | ## Behavior Modeling @@ -303,21 +294,21 @@ its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. -#### Microsoft Graph rules for modeling behavior - -| Requirements | Severity | -|-----------------------------------------------------------------------------------------------------------------|----------| -| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets | Error | -| :heavy_check_mark: **MUST** use PATCH to edit updatable resources | Error | -| :heavy_check_mark: **MUST** use DELETE to delete deletable resources | Error | -| :heavy_check_mark: **MUST** return a Location header with the edit URL or read URL of a created resource | Error | -| :heavy_check_mark: **MUST** use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | Error | -| :heavy_check_mark: **MUST** use POST …/{collection} for creating resources. | Error | -| :heavy_check_mark: **MUST** use PATCH …/{collection}/{id} for updating resources. | Error | -| :warning: **SHOULD NOT** use PUT …/{collection}/{id} for updating resources. | Warning | -| :no_entry: **MUST NOT** use PATCH to replaces resources or PUT to partially update resources. | Error | -| :warning: **SHOULD NOT** use patterns that require multiple round trips to complete a single logical action. | Warning | -| :ballot_box_with_check: **MAY** supporting return and omit-nulls preferences. | Warning | +#### Microsoft Graph rules for modeling behavior COMBINE ACCORDINGLY +COMBINE ACCORDINGLY +| Microsoft Graph Requirements for modeling behavior | +|-----------------------------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets | +| :heavy_check_mark: **MUST** use PATCH to edit updatable resources | +| :heavy_check_mark: **MUST** use DELETE to delete deletable resources | +| :heavy_check_mark: **MUST** use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | +| :heavy_check_mark: **MUST** use POST …/{collection} for creating resources. | +| :heavy_check_mark: **MUST** use PATCH …/{collection}/{id} for updating resources. | +| :warning: **SHOULD NOT** use PUT …/{collection}/{id} for updating resources. | +| :no_entry: **MUST NOT** use PATCH to replaces composite resources | +| :no_entry: **MUST NOT** PUT to partially update resources. | +| :warning: **SHOULD NOT** use patterns that require multiple round trips to complete a single logical action. | +COMBINE ACCORDINGLY For a complete list of standard HTTP operations you can refer to the [Microsoft REST API @@ -326,9 +317,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m ### Error Handling Microsoft REST API Guidelines provide guidelines that Microsoft REST APIs should -follow when returning error condition responses. However, the structure, form -and content of the error response payloads is currently not enforced leading to -undiscoverable and inconsistent error messages. You can improve API traceability +follow when returning error condition responses. You can improve API traceability and consistency by using recommended Graph error model: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -336,18 +325,11 @@ and consistency by using recommended Graph error model: "error": { "code": "BadRequest", "message": "Unsupported functionality", - "target": "query", - "details": [ - { - "code": "301", - "target": "\$search", - "message": "\$search query option not supported" - } - ], + "target": "query", "innererror": { "code": "301", "message": "Cannot process the request because a required field is missing.", - "stacktrace": [...], + } } } @@ -364,7 +346,7 @@ The following examples demonstrate error modeling for common use cases: "error": { "code": "badRequest", "message": "Cannot process the request because it is malformed or incorrect.", - "target": "Service X (Optional)" + "target": "Resource X (Optional)" } } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -382,19 +364,18 @@ The following examples demonstrate error modeling for common use cases: "message": "Cannot process the request because it is malformed or incorrect.", "innererror": { "code": "requiredFieldOrParameterMissing", - "message": "Cannot process the request because a required field or parameter is missing.", - "stacktrace": "[StackTrace]" + } } } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -| Microsoft Graph enforces the following error rules | Severity | -|-----------------------------------------------------------------------------------------------------------------------|----------| -| :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | Error | -| :heavy_check_mark: **MUST** return a 403 Forbidden error when insufficient scopes are present on the auth token. | Error | -| :heavy_check_mark: **MUST** return a 429 Too many requests error when the caller has exceeded throttling limits. | Error | -| :ballot_box_with_check: **MAY** returning a 404 Not found error if a 403 would result in information disclosure. | Warning | +| Microsoft Graph enforces the following error rules | +|-----------------------------------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | +| :heavy_check_mark: **MUST** return a 403 Forbidden error when insufficient scopes are present on the auth token. | +| :heavy_check_mark: **MUST** return a 429 Too many requests error when the caller has exceeded throttling limits. | +| :ballot_box_with_check: **SHOULD** returning a 404 Not found error if a 403 would result in information disclosure. | For a complete mapping of error codes to HTTP statuses you can refer to the [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). @@ -412,147 +393,43 @@ breaking change. - Addition of an annotation OpenType="true" Addition of properties that are nullable or have a default value - - Addition of a member to an evolvable enumeration 1. Removal, rename, or change to the type of an open extension - - Removal, rename, or change to the type of an annotation \*Introduction of paging to existing collections - - Changes to error codes Changes to the order of properties - - Changes to the length or format of opaque strings, such as resource IDs \*\* Breaking changes:\*\* - Changes to the URL or fundamental request/response associated with a resource - - Changing semantics of resource representation - - Removal, rename, or change to the type of a declared property - - Removal or rename of APIs or API parameters Addition of a required request header - - Addition of a EnumType members for non-extensible enumerations - - Addition of a Nullable="false" properties to existing types - - Addition of a Nullable="false" parameters to existing actions and functions - - Adding attributes to existing nodes is considered breaking. For the full list of rules you can refer to [this section of the OData V4 spec](https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209). ## Versioning and Deprecation +As the market and technology evolves your APIs will require changes in this case you must avoid breaking changes and add new resources and features incrementally. If it is not possible then you must version elements of your APIs. +Microsoft Graph allows versioning of elements including entities and properties. The versioning process goes along with deprecation and as soon as you introduce a new element update the previous version needs to follow the deprecation process before retirement. You must create a new version when: + - Renaming any element of your API or + - Restructuring a resource type. -When changes are imminent you need to support explicit versioning as it's -critical that clients can count on services to be stable over time, and it's -critical that services can add features and make changes. Microsoft Graph API -follows the guidance described in the Model Versioning section of the [Microsoft -REST API -guidelines](https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#12-versioning). - -The following versions of the Microsoft Graph API are currently available: - -1. API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in - general availability (GA) status. - -2. API sets on the beta endpoint (https://graph.microsoft.com/beta) are in beta - or private preview status. - -In general API breaking changes are not allowed in the GA version of Microsoft -Graph API. For beta API you can expect breaking changes and deprecation of APIs -from time to time. - -As new versions of the Microsoft Graph REST APIs and Microsoft Graph SDKs are -released, earlier versions will be retired. Microsoft declares a version as -deprecated at least 24 months in advance of retiring it. Similarly, for -individual APIs that are generally available (GA), Microsoft declares an API as -deprecated at least 24 months in advance of removing it from the GA version. - -### Deprecation Process - -If your API requires an introduction of breaking changes you must follow the -deprecation process: - -- After API review board approvals, add Revisions annotation to the API - definition CSDL with the following terms: - - - Kind of change: Deprecated (vs "added" to track added properties/types) - - - Human readable description of the change: Used in changelog, - documentation etc. - - - Version: Used to identify group of changes. Of the format - "YYYY-MM/Category" where "YYYY-MM" is the month the deprecation is - announced, and "Category" is the category under which the change is - described in the ChangeLog - - - Date: Date when the element was marked as deprecated - - - RemovalDate: Date when the element may be removed - -The annotation can be applied to a type, entity set, singleton, property, -navigation property, function or action. If a type is marked as deprecated, it -is not necessary to mark members of that type as deprecated, nor is it necessary -to annotate any usage of that type in entity sets, singletons, properties, -navigation properties, functions, or actions. - -**Example of property annotation:** - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - - - - - - - - - - - - ... - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When the request URL contains a reference to a deprecated model element, the -HTTP response includes a [Deprecation -header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the -date the element was marked as deprecated) and a Sunset header (with the date 2 -years beyond the Deprecation date). Response also includes a link header -pointing to the breaking changes page. - -**Deprecation header example:** - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Deprecation: Thursday, 30 June 2022 11:59:59 GMT -Sunset: Wed, 30 Mar 2022 23:59:59 GMT -Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2020-06-30_state ; rel="deprecation"; type="text/html"; title="state" -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Deprecation cadence:** - -- As an API developer you can mark individual API schema elements as - deprecated on a quarterly basis, after going through an API review and - approval process. Quarterly deprecation cadence will allow the services to - evolve schemas over time, without waiting for a coordinated, monolithic - endpoint change. - -- Once marked as deprecated, the elements must continue to be supported for a - minimum of 3 years before removal (or a minimum of 2 years if, based on - telemetry, the element is no longer being used). +Microsoft Graph provides two public endpoints to support API lifecycle: +1. API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in general availability (GA) status. +2. API sets on the beta endpoint (https://graph.microsoft.com/beta) are in beta or private preview status. -- Tools, documentation, SDKs, and other mechanisms are driven by this explicit - deprecation to reach out to customers that may be affected by the changes. +Microsoft Graph APIs in the GA version guarantee API stability and consistency for its clients. If your API requires a breaking change in GA, then you MUST create new element versions and support the deprecated elements for a minimum of 36 months. +On the beta endpoint breaking changes and deprecation of APIs are allowed with consideration of dependencies and customer impact. It is the best practice to test new element versions on the beta endpoint at first then promote API changes to the GA. +Detailed requirements for versioning and deprecation are described in the [Deprecation guidelines](./deprecation.md). -- APIs in beta or preview versions can use the same mechanism but are not - bound by the quarterly cadence or minimal support period before removal of - deprecated elements. ## Recommended API Patterns diff --git a/graph/ModelExample.png b/graph/ModelExample.png new file mode 100644 index 0000000000000000000000000000000000000000..c8481778f1df97a6284a5b64c581dae3fd3300a0 GIT binary patch literal 66461 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 z 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 + + + + + + + + + + + + ... + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When the request URL contains a reference to a deprecated model element, the +HTTP response includes a [Deprecation +header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the +date the element was marked as deprecated) and a Sunset header (with the date 2 +years beyond the Deprecation date). Response also includes a link header +pointing to the breaking changes page. + +**Deprecation header example:** + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Deprecation: Thursday, 30 June 2022 11:59:59 GMT +Sunset: Wed, 30 Mar 2022 23:59:59 GMT +Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2020-06-30_state ; rel="deprecation"; type="text/html"; title="state" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Deprecation sequence:** + +- As an API developer you can mark individual API schema elements as + deprecated + TODO + deprecation cadence will allow the services to + evolve schemas over time, without waiting for a coordinated, monolithic + endpoint change. + +- Once marked as deprecated, the elements must continue to be supported for a + minimum of 3 years before removal (or a minimum of 2 years if, based on + telemetry, the element is no longer being used). + +- Tools, documentation, SDKs, and other mechanisms are driven by this explicit + deprecation to reach out to customers that may be affected by the changes. + +- APIs in beta or preview versions can use the same mechanism but are not + bound by the quarterly cadence or minimal support period before removal of + deprecated elements. \ No newline at end of file From 9bda0968550985db9370fcdfcaf7f403fec76005 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 14 Dec 2021 09:56:10 -0500 Subject: [PATCH 211/729] Updated to add link to breaking changes doc --- azure/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure/README.md b/azure/README.md index 14a9db60..5cebe7b6 100644 --- a/azure/README.md +++ b/azure/README.md @@ -5,7 +5,8 @@ Designing powerful APIs with strong defaults, consistent behavior across related * [Considerations for Service Design](ConsiderationsForServiceDesign.md) * [Guidelines](Guidelines.md) * [OpenAPI Style Guidelines](https://github.com/Azure/azure-api-style-guide/blob/main/README.md) +* [Breaking Changes](https://aka.ms/azapi/breakingchanges) You can reach out to use via [email](mailto://azureapirbcore@microsoft.com) or in our [Teams](https://teams.microsoft.com/l/team/19%3a3ebb18fded0e47938f998e196a52952f%40thread.tacv2/conversations?groupId=1a10b50c-e870-4fe0-8483-bf5542a8d2d8&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) channel. -Note: The Teams channel is internal MS. \ No newline at end of file +Note: The Teams channel is internal MS. From ca4ca980c4f0cc503bf766b0505967dbbee828fe Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 14 Dec 2021 16:47:46 -0500 Subject: [PATCH 212/729] formatting --- graph/GuidelinesGraph.md | 27 +++++++++++++-------------- graph/deprecation.md | 20 -------------------- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 496820e9..337c38ee 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -38,23 +38,19 @@ across your products will make the difference between success and failure of your ecosystem. This document offers guidance that Graph API developer teams MUST follow to -ensure that customers have a great experience. A new API design should meet the +ensure that Microsoft Graph has a consistent and easy to use API surface. A new API design should meet the following goals: \- Developer friendly via consistent naming, patterns, and web standards (HTTP, REST, JSON) -\- Efficient and cost-effective. - \- Work well with SDKs in many programming languages. -\- Sustainable & versionable via clear API contracts. +\- Sustainable & evolvable via clear API contracts. The Microsoft Graph guidelines are an extension of the Microsoft REST API -guidelines. Readers are assumed also be reading the Microsoft REST API -guidelines and be familiar with them. Graph guidance is a superset of the -Microsoft API guidelines and services should follow them except where this -document outlines specific differences or exceptions to those guidelines. +guidelines. Readers are assumed also be reading and following the Microsoft REST API +guidelines except where this document outlines specific differences or exceptions to those guidelines. Together these guidelines and a library of API patterns serve as the means by which API teams discuss and come to consensus on API review recommendations. @@ -248,7 +244,7 @@ Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conv | Additional Microsoft Graph rules for modeling resources | |----------------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** use String type for ID | -| :ballot_box_with_check: **SHOULD** use a primary key composed of a single property and not multiple. | +| :ballot_box_with_check: **SHOULD** use a primary key composed of a single property | | :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | | :heavy_check_mark: **MUST** use a value property in the root object to return a collection | | :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | @@ -316,7 +312,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m ### Error Handling -Microsoft REST API Guidelines provide guidelines that Microsoft REST APIs should +Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should follow when returning error condition responses. You can improve API traceability and consistency by using recommended Graph error model: @@ -335,6 +331,7 @@ and consistency by using recommended Graph error model: } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The top-level error code must be aligned with HTTP response status codes according to [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). The following examples demonstrate error modeling for common use cases: - **Simple error**: A workload wants to report an error with top-level details @@ -375,7 +372,7 @@ The following examples demonstrate error modeling for common use cases: | :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | | :heavy_check_mark: **MUST** return a 403 Forbidden error when insufficient scopes are present on the auth token. | | :heavy_check_mark: **MUST** return a 429 Too many requests error when the caller has exceeded throttling limits. | -| :ballot_box_with_check: **SHOULD** returning a 404 Not found error if a 403 would result in information disclosure. | +| :ballot_box_with_check: **SHOULD** return a 404 Not found error if a 403 would result in information disclosure. | For a complete mapping of error codes to HTTP statuses you can refer to the [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). @@ -417,17 +414,19 @@ For the full list of rules you can refer to [this section of the OData V4 spec](https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209). ## Versioning and Deprecation -As the market and technology evolves your APIs will require changes in this case you must avoid breaking changes and add new resources and features incrementally. If it is not possible then you must version elements of your APIs. +As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If it is not possible then you must version elements of your APIs. Microsoft Graph allows versioning of elements including entities and properties. The versioning process goes along with deprecation and as soon as you introduce a new element update the previous version needs to follow the deprecation process before retirement. You must create a new version when: - Renaming any element of your API or - Restructuring a resource type. +If the current element name is best, a new property is added that has the existing name plus the suffix _v2, indicating that it is the successor to the original property. The original property is then marked as deprecated using annotations. + Microsoft Graph provides two public endpoints to support API lifecycle: 1. API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in general availability (GA) status. 2. API sets on the beta endpoint (https://graph.microsoft.com/beta) are in beta or private preview status. -Microsoft Graph APIs in the GA version guarantee API stability and consistency for its clients. If your API requires a breaking change in GA, then you MUST create new element versions and support the deprecated elements for a minimum of 36 months. -On the beta endpoint breaking changes and deprecation of APIs are allowed with consideration of dependencies and customer impact. It is the best practice to test new element versions on the beta endpoint at first then promote API changes to the GA. +Microsoft Graph APIs in the GA version guarantee API stability and consistency for its clients. If your API requires a breaking change in GA, then you MUST create new element versions and support deprecated elements for a minimum of 36 months. +On the beta endpoint breaking changes and deprecation of APIs are allowed with consideration of dependencies and customer impact. It is best practice to test new element versions on the beta endpoint at first then promote API changes to the GA endpoint. Detailed requirements for versioning and deprecation are described in the [Deprecation guidelines](./deprecation.md). diff --git a/graph/deprecation.md b/graph/deprecation.md index 5ae719e4..549f751e 100644 --- a/graph/deprecation.md +++ b/graph/deprecation.md @@ -59,23 +59,3 @@ Deprecation: Thursday, 30 June 2022 11:59:59 GMT Sunset: Wed, 30 Mar 2022 23:59:59 GMT Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2020-06-30_state ; rel="deprecation"; type="text/html"; title="state" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Deprecation sequence:** - -- As an API developer you can mark individual API schema elements as - deprecated - TODO - deprecation cadence will allow the services to - evolve schemas over time, without waiting for a coordinated, monolithic - endpoint change. - -- Once marked as deprecated, the elements must continue to be supported for a - minimum of 3 years before removal (or a minimum of 2 years if, based on - telemetry, the element is no longer being used). - -- Tools, documentation, SDKs, and other mechanisms are driven by this explicit - deprecation to reach out to customers that may be affected by the changes. - -- APIs in beta or preview versions can use the same mechanism but are not - bound by the quarterly cadence or minimal support period before removal of - deprecated elements. \ No newline at end of file From 07b43e1166cb2d9d1b159f8bb3d67b29228015b9 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 15 Dec 2021 14:26:27 -0500 Subject: [PATCH 213/729] template update --- graph/GuidelinesGraph.md | 2 +- graph/Modelling with Subtypes Pattern.md | 15 ++++----------- graph/PatternDescriptionTemplate.md | 14 ++++++-------- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 337c38ee..0da2979b 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -54,7 +54,7 @@ guidelines except where this document outlines specific differences or exception Together these guidelines and a library of API patterns serve as the means by which API teams discuss and come to consensus on API review recommendations. -This document borrows heavily from multiple public sources such as: +This document borrows from multiple public sources such as: 1. Microsoft Azure REST API Guidelines diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index 2fe0afd6..d6def3aa 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -6,17 +6,12 @@ Not supposed to be precise but easy to understand  *A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - -## Context - - These entities can be represented as a type hierarchy similar to the OOP inheritance concept. The hierarchy will be modeled as a base type and multiple subtypes which represent variations in metadata and behavior. In this model common elements of the base type can be reused, extended, or modified by subtypes. - +
    ## Problem -How to design API ... it should be straightforward to add new variants to the API as needed in the future. - +-------- +The API design requires to model a set of entities based on a common concept that can be further grouped into mutually exclusive variants with specific properties and behaviors. The API design should be evolvable and allow addition of new variants without breaking changes. @@ -27,9 +22,7 @@ How to design API ... it should be straightforward to add new variants to the AP OData allows us to design collections of entities (entity sets, multi valued navigation properties) with values of different types using **type hierarchy**, where there is one abstract base type with a few -common properties and one sub-type for each variant of the entity. In -the current version of Microsoft Graph there are many collections of -items that represent slightly different things, variants of one concept. +common properties and one sub-type for each variant of the entity. ## Issues and Considerations ------------------------- diff --git a/graph/PatternDescriptionTemplate.md b/graph/PatternDescriptionTemplate.md index 639f5c42..2ae4f552 100644 --- a/graph/PatternDescriptionTemplate.md +++ b/graph/PatternDescriptionTemplate.md @@ -6,15 +6,10 @@ Microsoft Graph API Design Pattern *Provide a short description of the pattern.* -## Context - -*Describe business context relevant for the pattern.* - -* * ## Problem -------- - +*Describe business context relevant for the pattern.* *Provide a short description of the problem.* * * @@ -31,19 +26,22 @@ Microsoft Graph API Design Pattern *Describe when and why the solution is applicable and when it may not.* +* * + ## Issues and Considerations ------------------------- *Describe tradeoffs of the solution.* -  +  +* * ## Example ------- *Provide a short example from real life* -  +* *    From b71a879395b37de5e1e9ecbe88ecd58a8f5c5e83 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 15 Dec 2021 15:02:13 -0500 Subject: [PATCH 214/729] grammar --- graph/GuidelinesGraph.md | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 0da2979b..6c36769d 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -13,7 +13,7 @@ Table of Contents - [Query Support](#query-support) - [Recommended Modeling Patterns](#recommended-modeling-patterns) - [Behavior Modeling](#behavior-modeling) - - [Microsoft Graph rules for modeling behavior COMBINE ACCORDINGLY](#microsoft-graph-rules-for-modeling-behavior-combine-accordingly) + - [Microsoft Graph rules for modeling behavior](#microsoft-graph-rules-for-modeling-behavior) - [Error Handling](#error-handling) - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) - [Versioning and Deprecation](#versioning-and-deprecation) @@ -26,7 +26,7 @@ Table of Contents | Date | Notes | |-------------|-----------------------------| -| 2021-Sep-28 | Alignment with Azure style. | +| 2021-Sep-28 | Using summary and patterns style. | | 2020-Oct-04 | Initial version in Wiki. | ## Introduction @@ -115,7 +115,7 @@ To create a good API you need to start with understanding your **use cases** and supporting domain model. We describe domain models in terms of entities or resources, their properties, and relationships and further refer to it as entity data model. There is no one-to-one correspondence between domain model elements -and API resources as APIs usually support only customer-facing use cases. A simple resource diagram makes it easier to reason about resource relationships and a shape of your API. +and API resources as APIs usually support only customer-facing use cases. A simple resource diagram, like below, makes it easier to reason about resource relationships and a shape of your API. ![Resource model example](ModelExample.png) @@ -252,8 +252,8 @@ Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conv ### Recommended Modeling Patterns -There are different approaches to design an API resource model in situations -with multiple variants of common concept. Type Hierarchy, Facets, and Flat bag +There are different approaches for designing an API resource model in situations +with multiple variants of a common concept. Type Hierarchy, Facets, and Flat bag of properties are three most often used patterns in Microsoft Graph today: - Type hierarchy is represented by one abstract base type with a few common @@ -274,12 +274,10 @@ of properties are three most often used patterns in Microsoft Graph today: The following table shows summary of main qualities for each pattern and will help to select a pattern preferred for your use case. -| Use case | | | | Recommended Pattern | - -| API qualities\ | Properties and behavior described in metadata | Suited for combinations of properties and behaviors | Simple query construction | Syntactical backward compatible | -|------------------------|-----------------------------------------------|-------------------------------------|---------------------------|---------------------------------|---------------------------| -| Use Case 1 | yes | no | no | yes |Type hierarchy | +| API qualities\
    Use Cases | Properties and behavior described in metadata | Suited for combinations of properties and behaviors | Simple query construction | Syntactically backward compatible |Recommended Pattern | +|----------------------|------------------------------------------|----------------------------------------|---------------------------|---------------------------------|---------------------------| +| Use Case 1 | yes | no | no | yes |Type hierarchy | | Use Case 2 | ok | yes | yes | yes |Facets | | Use Case 3 | no | no | yes | yes |Flat bag | @@ -290,21 +288,17 @@ its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. -#### Microsoft Graph rules for modeling behavior COMBINE ACCORDINGLY -COMBINE ACCORDINGLY -| Microsoft Graph Requirements for modeling behavior | +### Microsoft Graph rules for modeling behavior + +| Requirements | |-----------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets | +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collection …/{collection} | | :heavy_check_mark: **MUST** use PATCH to edit updatable resources | | :heavy_check_mark: **MUST** use DELETE to delete deletable resources | | :heavy_check_mark: **MUST** use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | -| :heavy_check_mark: **MUST** use POST …/{collection} for creating resources. | -| :heavy_check_mark: **MUST** use PATCH …/{collection}/{id} for updating resources. | | :warning: **SHOULD NOT** use PUT …/{collection}/{id} for updating resources. | -| :no_entry: **MUST NOT** use PATCH to replaces composite resources | -| :no_entry: **MUST NOT** PUT to partially update resources. | -| :warning: **SHOULD NOT** use patterns that require multiple round trips to complete a single logical action. | -COMBINE ACCORDINGLY +| :no_entry: **MUST NOT** use PATCH to replaces composite resources | | +|:ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical action. | For a complete list of standard HTTP operations you can refer to the [Microsoft REST API From fe034d3270c3d02a7864ced8b1a92f6ed9965537 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 17 Dec 2021 13:09:43 -0500 Subject: [PATCH 215/729] first statement --- graph/GuidelinesGraph.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 6c36769d..5a8b63ce 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -31,11 +31,7 @@ Table of Contents ## Introduction -When building a digital ecosystem you should use API-first approach and start -with design and development of your APIs. Considering API usability and creating -APIs that are easy to discover, simple to use, fit to purpose, and consistent -across your products will make the difference between success and failure of -your ecosystem. +When building a digital ecosystem API usability becomes a business priority. Success of your ecosystem depends on APIs that are easy to discover, simple to use, fit to purpose, and consistent across your products. This document offers guidance that Graph API developer teams MUST follow to ensure that Microsoft Graph has a consistent and easy to use API surface. A new API design should meet the From 5be483934f9e4a0881978a005ba0ef36469160ff Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 17 Dec 2021 17:07:47 -0500 Subject: [PATCH 216/729] added finctions and actions --- graph/GuidelinesGraph.md | 76 +++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 44 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 5a8b63ce..a58185e0 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -81,19 +81,11 @@ review. ## Design Approach -The design of your API is arguably the most important investment you will make -in it. The design of your API is what creates the first impression for -developers. Microsoft Graph APIs follow HTTP, REST, and JSON standards and are -described using ODATA conventions and CSDL for schema definition (see -[Documentation · OData - the Best Way to -REST](https://www.odata.org/documentation/)). - -We promote API-first design approach where you begin by creating an interface or -API for your service first. Subsequently you follow with the service -implementation which relies on the specified interface. API -first approach is -essential for agility, predictability, and reuse of your APIs as it promotes -good understanding of your modeling domain, consistent interface contract, and -understanding of how supporting service will evolve. +The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. +We promote API-first design approach where you begin your product design by creating an interface contract for your service first and later you follow with the service design and implementation to support the interface. This approach ensures decoupling of the interface and implementation and is +essential for agility, predictability, and reuse of your APIs. Starting with user-facing contracts also promotes good understanding of user interactions, your modeling domain, and understanding of how supporting service will evolve. +Microsoft Graph supports RESTful API style which follows HTTP, REST, and JSON standards, where API contract is described using ODATA conventions and CSDL for schema definition (see +[Documentation · OData - the Best Way to REST](https://www.odata.org/documentation/)). In general API design includes the following steps: @@ -111,19 +103,12 @@ To create a good API you need to start with understanding your **use cases** and supporting domain model. We describe domain models in terms of entities or resources, their properties, and relationships and further refer to it as entity data model. There is no one-to-one correspondence between domain model elements -and API resources as APIs usually support only customer-facing use cases. A simple resource diagram, like below, makes it easier to reason about resource relationships and a shape of your API. +and API resources as APIs usually support only customer-facing use cases. A simple resource diagram, like below, makes it easier to reason about resource relationships and the shape of your API. ![Resource model example](ModelExample.png) -After API resources are identified you need to name them and their properties so -that the API will be discoverable and intuitive for developers, and consistent -with other Graph resources. - -When resources are defined it’s time to think about the behavior of your API and -define required operations and actions. There are read-only and write scenarios -where a resource can be used to represent some kind of data processing -operation. The terms function and action are used to identify read and write -operation style resources, respectively. +While designing your API resources you need to name them and their properties so that it will be intuitive for developers, consistent with other Graph resources and within the product domain. +After resources are defined it’s time to think about the behavior of your API which can be expressed via HTTP methods and operational resources such as functions and actions. At every step of your design you need to consider security, privacy and compliance as an intrinsic components of your API implementation. And finally @@ -132,11 +117,9 @@ to identify potential error scenarios with secure and descriptive messaging. ### Naming -Consistent naming is foundational for API usability. API resources are typically -described by nouns. You need to consider that resources and property names -appear in API URLs and payloads and should be descriptive and easy to -understand. Microsoft Graph naming conventions follow [Microsoft REST API -Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md). +API resources and their properties are typically described by nouns and appear in API URLs, input parameters,and output resources therefor should be descriptive and easy to understand. Microsoft Graph naming conventions follow [Microsoft REST API +Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md). When thinking about naming consistency you should consider consistency with industry standards, product namespace and other Graph APIs. + Below is a short summary of the most often used conventions. | Requirements | Example | @@ -282,23 +265,27 @@ help to select a pattern preferred for your use case. The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request / -response pattern should be one of the first implementation decisions you make. +response pattern should be one of the first implementation decisions you make. You also may utilize operational resources such as functions and actions. According to [ODATA standards]( http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2) a function represents an operation which returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action may have side effects and may return a result represented as a single entity or collection of any type. ### Microsoft Graph rules for modeling behavior | Requirements | |-----------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collection …/{collection} | +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collection …/{collection}| | :heavy_check_mark: **MUST** use PATCH to edit updatable resources | | :heavy_check_mark: **MUST** use DELETE to delete deletable resources | | :heavy_check_mark: **MUST** use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | | :warning: **SHOULD NOT** use PUT …/{collection}/{id} for updating resources. | | :no_entry: **MUST NOT** use PATCH to replaces composite resources | | -|:ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical action. | +| :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical action. | +| :no_entry: **MUST NOT** use unbounded actions and functions| + +As Microsoft Graph supports only bound operations they must have a binding parameter matching the type of the bound resource. The binding parameter MAY be Nullable. +In addition both actions and functions support overloading, meaning an API definition may contain multiple actions or functions with the same name. +Microsoft Graph supports the use of optional parameters. You can use the optional parameter annotation instead of creating function or action overloads when unnecessary. For a complete list of standard HTTP operations you can refer to the [Microsoft -REST API -Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). +REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). ### Error Handling @@ -360,7 +347,7 @@ The following examples demonstrate error modeling for common use cases: | Microsoft Graph enforces the following error rules | |-----------------------------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | -| :heavy_check_mark: **MUST** return a 403 Forbidden error when insufficient scopes are present on the auth token. | +| :heavy_check_mark: **MUST** return a 403 Forbidden error when insufficient scopes are present in the auth token. | | :heavy_check_mark: **MUST** return a 429 Too many requests error when the caller has exceeded throttling limits. | | :ballot_box_with_check: **SHOULD** return a 404 Not found error if a 403 would result in information disclosure. | @@ -378,13 +365,14 @@ breaking change. \*\* Non-breaking changes:\*\* -- Addition of an annotation OpenType="true" Addition of properties that are - nullable or have a default value -- Addition of a member to an evolvable enumeration 1. Removal, rename, or - change to the type of an open extension -- Removal, rename, or change to the type of an annotation \*Introduction of - paging to existing collections -- Changes to error codes Changes to the order of properties +- Addition of an annotation OpenType="true" +- Addition of properties that are nullable or have a default value +- Addition of a member to an evolvable enumeration +- Removal, rename, or change to the type of an open extension +- Removal, rename, or change to the type of an annotation +- Introduction of paging to existing collections +- **Changes to error codes?????** +- Changes to the order of properties - Changes to the length or format of opaque strings, such as resource IDs \*\* Breaking changes:\*\* @@ -393,12 +381,12 @@ breaking change. resource - Changing semantics of resource representation - Removal, rename, or change to the type of a declared property -- Removal or rename of APIs or API parameters Addition of a required request - header +- Removal or rename of APIs or API parameters +- Addition of a required request header - Addition of a EnumType members for non-extensible enumerations - Addition of a Nullable="false" properties to existing types - Addition of a Nullable="false" parameters to existing actions and functions -- Adding attributes to existing nodes is considered breaking. +- **Adding attributes to existing nodes is considered breaking??** For the full list of rules you can refer to [this section of the OData V4 spec](https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209). From 4babac52be9c64db2156df4f2aef175a5c792ab5 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 17 Dec 2021 17:16:38 -0500 Subject: [PATCH 217/729] Updated TOC --- graph/GuidelinesGraph.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index a58185e0..f96b68a7 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -11,12 +11,11 @@ Table of Contents - [Naming](#naming) - [Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) - [Query Support](#query-support) - - [Recommended Modeling Patterns](#recommended-modeling-patterns) - - [Behavior Modeling](#behavior-modeling) - - [Microsoft Graph rules for modeling behavior](#microsoft-graph-rules-for-modeling-behavior) + - [Resource Modeling Patterns](#resource-modeling-patterns) + - [Behavior Modeling](#behavior-modeling) - [Error Handling](#error-handling) - - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) - - [Versioning and Deprecation](#versioning-and-deprecation) + - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) + - [Versioning and Deprecation](#versioning-and-deprecation) - [Recommended API Patterns](#recommended-api-patterns) - [References](#references) @@ -229,7 +228,7 @@ Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conv | :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | | :warning: **SHOULD NOT** add the property id to a complex type | -### Recommended Modeling Patterns +### Resource Modeling Patterns There are different approaches for designing an API resource model in situations with multiple variants of a common concept. Type Hierarchy, Facets, and Flat bag @@ -260,16 +259,14 @@ help to select a pattern preferred for your use case. | Use Case 2 | ok | yes | yes | yes |Facets | | Use Case 3 | no | no | yes | yes |Flat bag | -## Behavior Modeling +### Behavior Modeling The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. You also may utilize operational resources such as functions and actions. According to [ODATA standards]( http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2) a function represents an operation which returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action may have side effects and may return a result represented as a single entity or collection of any type. -### Microsoft Graph rules for modeling behavior - -| Requirements | +| Microsoft Graph rules for modeling behavior | |-----------------------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collection …/{collection}| | :heavy_check_mark: **MUST** use PATCH to edit updatable resources | @@ -354,7 +351,7 @@ The following examples demonstrate error modeling for common use cases: For a complete mapping of error codes to HTTP statuses you can refer to the [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). -### API contract and non-backward compatible changes +## API contract and non-backward compatible changes Microsoft Graph definition of breaking changes is based on the [Microsoft REST API @@ -391,7 +388,7 @@ breaking change. For the full list of rules you can refer to [this section of the OData V4 spec](https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209). -## Versioning and Deprecation +### Versioning and Deprecation As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If it is not possible then you must version elements of your APIs. Microsoft Graph allows versioning of elements including entities and properties. The versioning process goes along with deprecation and as soon as you introduce a new element update the previous version needs to follow the deprecation process before retirement. You must create a new version when: - Renaming any element of your API or From eb9254a42856869343cc84498b7f1e01aff6d6d3 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 20 Dec 2021 13:38:20 -0500 Subject: [PATCH 218/729] editing --- graph/GuidelinesGraph.md | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index f96b68a7..c7a15bda 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -80,10 +80,7 @@ review. ## Design Approach -The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. -We promote API-first design approach where you begin your product design by creating an interface contract for your service first and later you follow with the service design and implementation to support the interface. This approach ensures decoupling of the interface and implementation and is -essential for agility, predictability, and reuse of your APIs. Starting with user-facing contracts also promotes good understanding of user interactions, your modeling domain, and understanding of how supporting service will evolve. -Microsoft Graph supports RESTful API style which follows HTTP, REST, and JSON standards, where API contract is described using ODATA conventions and CSDL for schema definition (see +The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote API-first design approach where you begin your product design by focusing on how information will be exchanged and represented and creating an interface contract for your API which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface and is essential for agility, predictability, and reuse of your APIs. Starting with user-facing contracts also promotes good understanding of user interactions, your modeling domain, and understanding of how the service will evolve. Microsoft Graph supports resource and query based API styles which follow HTTP, REST, and JSON standards, where API contract is described using ODATA conventions and schema definition (see Documentation · OData - the Best Way to REST). [Documentation · OData - the Best Way to REST](https://www.odata.org/documentation/)). In general API design includes the following steps: @@ -98,26 +95,16 @@ In general API design includes the following steps: - Specify errors -To create a good API you need to start with understanding your **use cases** and -supporting domain model. We describe domain models in terms of entities or -resources, their properties, and relationships and further refer to it as entity -data model. There is no one-to-one correspondence between domain model elements -and API resources as APIs usually support only customer-facing use cases. A simple resource diagram, like below, makes it easier to reason about resource relationships and the shape of your API. - +When creating your API contract you will define resources based on the domain model supporting your service and identify interactions based on use cases. Therefore it is essential to understand and document your use cases as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because APIs usually are often simplified to support only customer-facing use cases and obfuscate implementation details. We recommend creating a simple resource diagram, like below, to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. ![Resource model example](ModelExample.png) -While designing your API resources you need to name them and their properties so that it will be intuitive for developers, consistent with other Graph resources and within the product domain. -After resources are defined it’s time to think about the behavior of your API which can be expressed via HTTP methods and operational resources such as functions and actions. +After resources are defined it’s time to think about the behavior of your API which can be expressed via HTTP methods and operational resources such as functions and actions. As you think about API behavior you identify a happy path and various exceptions and deviations which will be expressed as errors and represented using HTTP codes and error messages. +At every step of your design you need to consider security, privacy and compliance as an intrinsic components of your API implementation. -At every step of your design you need to consider security, privacy and -compliance as an intrinsic components of your API implementation. And finally -based on your API resources, their behavior, and anticipated exceptions you need -to identify potential error scenarios with secure and descriptive messaging. ### Naming -API resources and their properties are typically described by nouns and appear in API URLs, input parameters,and output resources therefor should be descriptive and easy to understand. Microsoft Graph naming conventions follow [Microsoft REST API -Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md). When thinking about naming consistency you should consider consistency with industry standards, product namespace and other Graph APIs. +API resources are typically described by nouns. Resource and properties names appear in API URLs and payloads and must be descriptive and easy to understand for developers. Ease of understanding comes from familiarity and recognition therefore when thinking about naming you should consider consistency with industry standards, product namespace and other Graph APIs. Microsoft Graph naming conventions follow Microsoft REST API Guidelines. Below is a short summary of the most often used conventions. From aa4fb868c3db808079a109042a3726c8bc04e1fe Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 20 Dec 2021 17:37:59 -0500 Subject: [PATCH 219/729] added entity and complex --- graph/Modelling with Facets Pattern.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md index 7e03418f..e9624717 100644 --- a/graph/Modelling with Facets Pattern.md +++ b/graph/Modelling with Facets Pattern.md @@ -107,8 +107,9 @@ properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). ## Example -GET +`GET +` returns a collection where each element can be a user or a service principal, and has an additional property @odata.type to show subtype for each variant: From 6ee44439bba01e122a203857976f079f2f95aeca Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 20 Dec 2021 17:38:42 -0500 Subject: [PATCH 220/729] type hierarchy --- graph/GuidelinesGraph.md | 38 ++++- graph/Modelling with Subtypes Pattern.md | 207 ++++++++++++----------- 2 files changed, 141 insertions(+), 104 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index c7a15bda..37edda02 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -206,17 +206,29 @@ GET, and pass the query options part of the URL in the request body as described in the chapter [OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). -| Additional Microsoft Graph rules for modeling resources | -|----------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use String type for ID | -| :ballot_box_with_check: **SHOULD** use a primary key composed of a single property | -| :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | -| :heavy_check_mark: **MUST** use a value property in the root object to return a collection | -| :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | -| :warning: **SHOULD NOT** add the property id to a complex type | ### Resource Modeling Patterns +You can model complex resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity types declare a key property to uniquely identify its objects and Complex Types don't. In Microsoft Graph this key property has "id" as a prescribed name. +Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must use Entity Type to model addressable resources. Complex types are better suited to represent composite properties of API entities. + +```XML + + + + + + + + + + + + + + +``` + There are different approaches for designing an API resource model in situations with multiple variants of a common concept. Type Hierarchy, Facets, and Flat bag of properties are three most often used patterns in Microsoft Graph today: @@ -246,6 +258,16 @@ help to select a pattern preferred for your use case. | Use Case 2 | ok | yes | yes | yes |Facets | | Use Case 3 | no | no | yes | yes |Flat bag | +| Additional Microsoft Graph rules for modeling resources | +|----------------------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** use String type for ID | +| :ballot_box_with_check: **SHOULD** use a primary key composed of a single property | +| :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | +| :heavy_check_mark: **MUST** use a value property in the root object to return a collection | +| :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | +| :warning: **SHOULD NOT** add the property id to a complex type | + + ### Behavior Modeling The HTTP operations dictate how your API behaves. The URL of an API, along with diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index d6def3aa..a0a71b39 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -1,123 +1,138 @@ -# Type Hierarchy +# Type Hierarchy Microsoft Graph API Design Pattern -Not supposed to be precise but easy to understand  - -*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -
    +“Not supposed to be precise but easy to understand” +## *A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* ## Problem --------- -The API design requires to model a set of entities based on a common concept that can be further grouped into mutually exclusive variants with specific properties and behaviors. The API design should be evolvable and allow addition of new variants without breaking changes. - - +The API design requires to model a set of entities based on a common concept +that can be further grouped into mutually exclusive variants with specific +properties and behaviors. The API design should be evolvable and allow addition +of new variants without breaking changes. ## Solution --------- -OData allows us to design collections of entities (entity sets, multi -valued navigation properties) with values of different types using -**type hierarchy**, where there is one abstract base type with a few -common properties and one sub-type for each variant of the entity. +API designers can use OData **type hierarchy**, where there is one abstract base +type with a few shared properties representing the common concept and one +sub-type for each variant of the entity. ## Issues and Considerations -------------------------- - -When introducing a new subtype, you need to ensure that the new subtype -doesn't change the semantic of the type hierarchy with it's implicit -constraints. - -There are a **few potential risks** for client applications when new -sub-types are introduced: - -- De-serialization code might break because of missing -properties in returned collection items. Even though property X was -mandatory on all subtypes previously returned, the new subtype might not -have this property and the client code needs to deal with that. - -- Client libraries for strongly typed language might ignore some -of the values in the @odata.type property without further configuration -and need to be configured to be able to pick the right (client) type to -deserialize into. - -To minimize impact on clients type hierarchy can be refined by -annotating the collections with OData derived type constraints (see -validation vocabulary). This annotation restricts the values to certain -sub-trees of an inheritance hierarchy. It makes it very explicit that -the collection only contains elements of some of the subtypes and helps -to not return objects of a type that is semantically not suitable. In -addition, you can follow some of the mitigation techniques such as: - -- Avoid overgeneralized base types - -- Think about roll-out sequence - - Consider that Microsoft Graph does not return objects from a workload -that has a type that is not configured in current metadata. To avoid -inconsistencies, follow a two-step process: - - Introduce the entity type to the Graph metadata but don’t -return objects of the type in any of the heterogeneous collections. - - Enable your workload to return objects of the new type as items -of collection. - - -- Allow time for testing - - Inform the clients about the change and allow them to test the -changes in beta. Time is required to implement the code necessary to -deal with the new entity type, both in terms of de-serialization as well -as integrating it into the rest of the application. - -- Communicate the change in semantics - - - It is necessary for the client developers to incorporate the new -semantic into their application/service, even if the change is perceived -to be small. This requires early communication and clear documentation -of what the new type represents and why/how it is considered a subtype -of the original abstract type of the collection. + +When introducing a new subtype to the hierarchy, developers need to ensure that +the new subtype doesn't change the semantic of the type hierarchy with its +implicit constraints. + +There are a **few potential risks** for client applications when new sub-types +are introduced: + +- De-serialization code might break because of missing properties in returned + collection items. Even though property X was mandatory on all subtypes + previously returned, the new subtype might not have this property and the + client code needs to deal with that. + +- Client libraries for strongly typed language might ignore some of the values + in the @odata.type property without further configuration and need to be + configured to be able to pick the right (client) type to deserialize into. + +In addition, you can follow some of the mitigation techniques such as: + +- Think about roll-out sequence + + - Consider that Microsoft Graph does not return objects from a workload + that has a type that is not configured in current metadata. To avoid + inconsistencies, follow a two-step process: + + - Introduce the entity type to the Graph metadata but don’t return + objects of the type in any of the heterogeneous collections. + + - Enable your workload to return objects of the new type as items of + collection. + +- Allow time for testing + + - Inform the clients about the change and allow them to test the changes + in beta. Time is required to implement the code necessary to deal with + the new entity type, both in terms of de-serialization as well as + integrating it into the rest of the application. + +- Communicate the change in semantics + + - It is necessary for the client developers to incorporate the new + semantic into their application/service, even if the change is perceived + to be small. This requires early communication and clear documentation + of what the new type represents and why/how it is considered a subtype + of the original abstract type of the collection. ## When to Use this Pattern ------------------------- -The Type hierarchy pattern is well familiar to OOP developers and well -suited for strongly typed client programming languages. +The Type hierarchy pattern is well suited to use case where each variant of a +common concept has unique properties and behaviors, no combinations of variants +is anticipated, API queries are managed programmatically with type casting. There are related patterns to consider such as -[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) -and [Flat bag of +[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat +bag of properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). ## Example -------- -For example let’s assume you need to model an API to manage groups in an -organization, where employees can create groups and become owners of the -group by default. At the same time to support business processes some -groups may be created automatically by daemon applications using a -service principal account. In this case the service principle will -become the group owner. People and service principles have some common -and some unique properties such as both have unique identifiers and -credentials, but users will have additional properties such as email and -manager for example. Conversely a service principle won’t have a manager -assigned but may have an associated application identifier and a -description. -GET -[https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners](https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners)  -returns a collection where each element can be a user or a service -principal, and has an additional property @odata.type to show subtype -for each variant: + +The directoryObject type is the main abstraction for many directory +types such as users, organizational contacts, devices, service principals +and groups stored in Azure Active Directory. Since any directoryObject object is a unique entity the directoryObject type itself is derived from the graph.entity base type. + +```XML + + + + + + + + + +``` + + +Groups and users are derived types and modeled as + +```XML +  +        + +        +… + ``` + +API request to get members of a group returns a heterogeneous collection of +users and groups where each element can be a user or a group, and has an +additional property @odata.type for a variant subtype: + +```JSON +GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members?$select=id,displayName +… +Response payload: + { -    "@odata.context": + "@odata.context": "https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects",     "value": [ -        { -            "@**odata.type**": "\#**microsoft.graph.user**", -            "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", -            "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" -            // ... -        } +        {           + "@odata.type": "#microsoft.graph.user", + "id": "37ca648a-a007-4eef-81d7-1127d9be34e8", + "displayName": "John Cob" + }, + { + "@odata.type": "#microsoft.graph.group", + "id": "45f25951-d04f-4c44-b9b0-2a79e915658d", + "displayName": "Department 456" + }, +…     ] } ``` From 5f862575ca0885cf64d6998bf4604d33ef346e34 Mon Sep 17 00:00:00 2001 From: tg-msft Date: Mon, 20 Dec 2021 15:58:51 -0800 Subject: [PATCH 221/729] PR feedback --- azure/ConsiderationsForServiceDesign.md | 15 ++++++++------- azure/Guidelines.md | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 55b8adeb..17d3a03d 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -230,29 +230,30 @@ status monitor LRO, whereas the status of all RELO operations is combined into t So status monitor LROs are "one-to-one" with their operation status, whereas RELO-style LROs are "many-to-one". ## Errors -One of the most important parts of service design is also one of the most overlooked. The errors returned by your service are a critical part of your developer experience. Your service and your customer's application together form a distributed system. Errors are inevitable, but well-designed errors can help you avoid costly customer support incidents by empowering customers to self-diagnose problems. +One of the most important parts of service design is also one of the most overlooked. The errors returned by your service are a critical part of your developer experience and are part of your API contract. Your service and your customer's application together form a distributed system. Errors are inevitable, but well-designed errors can help you avoid costly customer support incidents by empowering customers to self-diagnose problems. -First, you should always try to design errors out of existence if possible. You'll get a lot of this for free by following the Guidelines. Some examples include: +First, you should always try to design errors out of existence if possible. You'll get a lot of this for free by following the [API Guidelines](https://aka.ms/azapi/guidelines). Some examples include: - Idempotent APIs solve a whole class of network issues where customers have no idea how to proceed if they send a request to the service but never get a response. -- Accessing resources from multiple microservices can quickly lead to complex race conditions so conditional requests provide optimistic concurrency for safe usage. -- Reframing the purpose of an API can obviate some errors. This is most often specific to your operations, but an example from the Guidelines is thinking about `DELETE`s as _"ensure no resource at this location exists"_ so they can return an easier to use `204` instead of _"delete this exact resource instance"_ which would fail with a non-idempotent `404`. +- Accessing resources from multiple microservices can quickly lead to complex race conditions. These can be avoided by supporting conditional requests through an [optimistic concurrency strategy](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#optimistic-concurrency), e.g. by leveraging `If-Match`/`If-None-Match` request headers. +- Reframing the purpose of an API can obviate some errors. This is most often specific to your operations, but an [example from the API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#http-return-codes) is thinking about `DELETE`s as _"ensure no resource at this location exists"_ so they can return an easier to use `204` instead of _"delete this exact resource instance"_ which would fail with a `404`. There are two types of errors returned from your service and customers handle them differently. - Usage errors where the customer is calling your API incorrectly. The customer can easily make these errors go away by fixing their code. We expect most usage errors to be found during testing. - Runtime errors that can't be prevented by the customer and need to be recovered from. Some runtime errors like `429` throttling will be handled automatically by client libraries, but most will be situations like a `409` conflict requiring knowledge about the customer's application to remedy. -We provide appropriate [HTTP status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses) for customers to handle errors generically and error code strings in our common error schema and the `x-ms-error-code` header for customers to handle errors specifically. As an example, consider what a customer would do when trying to get the properties of a Storage blob: +You should use appropriate [HTTP status codes](https://developer.mozilla.org/docs/Web/HTTP/Status#client_error_responses) for customers to handle errors generically and error code strings in our common error schema and the `x-ms-error-code` header for customers to handle errors specifically. As an example, consider what a customer would do when trying to get the properties of a Storage blob: - A `404` status code tells them the blob doesn't exist and the customer can report the error to their users - A `BlobNotFound` or `ContainerNotFound` error code will tell them why the blob doesn't exist so they can take steps to recreate it The common error schema in the Guidelines allows nested details and inner errors that have their own error codes, but the top-level error code is the most important. The HTTP status code and the top-level error code are the only part of your error that we consider part of your API contract that follows the same compatibility requirements as the rest of your API. Importantly, this means you **can not change the HTTP status code or top-level error code for an API in GA'ed service version**. You can only return new status codes and error codes in future API versions if customers make use of new features that trigger new classes of errors. Battle tested error handling is some of the hardest code to get right and we can't break that for customers when they upgrade to the latest version. The rest of the properties in your error like `message`, `details`, etc., are not considered part of your API contract and can change to improve the diagnosability of your service. -We also return the top-level error code as the `x-ms-error-code` header so client libraries have the ability to automatically retry requests when possible without having to parse a JSON payload. We recommend unique error codes like `ContainerBeingDeleted` for every distinct recoverable error that can occur, but suggest reusing common error codes like `InvalidHeaderValue` for usage errors where a descriptive error message is more important for resolving the problem. The Storage [Common](https://docs.microsoft.com/en-us/rest/api/storageservices/common-rest-api-error-codes) and [Blob](https://docs.microsoft.com/en-us/rest/api/storageservices/blob-service-error-codes) error codes are a good starting point if you're looking for examples. +You should also return the top-level error code as the `x-ms-error-code` header so client libraries have the ability to automatically retry requests when possible without having to parse a JSON payload. We recommend unique error codes like `ContainerBeingDeleted` for every distinct recoverable error that can occur, but suggest reusing common error codes like `InvalidHeaderValue` for usage errors where a descriptive error message is more important for resolving the problem. The Storage [Common](https://docs.microsoft.com/rest/api/storageservices/common-rest-api-error-codes) and [Blob](https://docs.microsoft.com/rest/api/storageservices/blob-service-error-codes) error codes are a good starting point if you're looking for examples. You can [define an enum in your spec](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/storage/data-plane/Microsoft.BlobStorage/preview/2021-04-10/blob.json#L10419) with `"modelAsString": true` that lists all of the top-level error codes to make it [easier for your customers to handle specific error codes](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/storage/Azure.Storage.Blobs#troubleshooting). -You should not use your OpenAPI/Swagger spec to document every error that can occur. The `"default"` response is the only thing AutoRest considers an error response unless you provide other annotations. Every unique status code turns into a separate code path in your client libraries so we do not encourage this practice. The only reason to document specific error codes is if they return a different error response than the default, but that is also heavily discouraged. You can [define an enum in your spec](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/storage/data-plane/Microsoft.BlobStorage/preview/2021-04-10/blob.json#L10419) with `"modelAsString": true` that lists all of the top-level error codes to make it [easier for your customers to handle specific error codes](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/storage/Azure.Storage.Blobs#troubleshooting). +You should not document specific error status codes in your OpenAPI/Swagger spec. The `"default"` response is the only thing AutoRest considers an error response unless you provide other annotations. Every unique status code turns into a separate code path in your client libraries so we do not encourage this practice. The only reason to document specific error status codes is if they return a different error response than the default, but that is also heavily discouraged. Be as precise as possible when writing error messages. A message with just `Invalid Argument` is almost useless to a customer who sent 100KB of JSON to your endpoint. ``Query parameter `top` must be less than or equal to 1000`` tells a customer exactly what went wrong so they can quickly fix the problem. Don't go overboard while writing great, understandable error messages and include any sensitive customer information or secrets though. Many developers will blindly write any error to logs that don't have the same level of access control as Azure resources. +All responses should include the `x-ms-request-id` header with a unique id for the request, but this is particularly important for error responses. Service logs for the request should contain the `x-ms-request-id` so that support staff can use this value to diagnose specific customer reported errors. Finally, write sample code for your service's workflow and add the code you'd want customers using to gracefully recover from errors. Is it actually graceful? Is it something you'd be comfortable asking most customers to write? We also highly encourage reaching out to customers during private preview and asking them for code they've written against your service. Their error handling might match your expectations, you might find a strong need for better documentation, or you might find important opportunities to improve the errors you're returning. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 84891cc8..9ee29877 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -294,7 +294,7 @@ There are 2 kinds of errors: *NOTE: `x-ms-error-code` values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future.* -:heavy_check_mark: **YOU MAY** implement the `x-ms-error-code` values as an enum with `"modelAsString": true` because it's possible add new values over time. In particular, it's only a breaking change if the same conditions result in a different top-level error code. +:heavy_check_mark: **YOU MAY** implement the `x-ms-error-code` values as an enum with `"modelAsString": true` because it's possible add new values over time. In particular, it's only a breaking change if the same conditions result in a *different* top-level error code. :warning: **YOU SHOULD NOT** add new top-level error codes to an existing API without bumping the service version. @@ -353,7 +353,7 @@ Example: *Note: Do not use this mechanism to provide information developers need to rely on in code (ex: the error message can give details about why you've been throttled, but the `Retry-After` should be what developers rely on to back off).* -:warning: **YOU SHOULD NOT** use your OpenAPI/Swagger specification to document every failing status code or error code for each operation. +:warning: **YOU SHOULD NOT** document specific error status codes in your OpenAPI/Swagger spec unless the "default" response cannot properly describe the specific error response (e.g. body schema is different). ### JSON Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: From f51d3271aea8e2c0f16d96adafdf5d29817e185f Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 21 Dec 2021 13:20:49 -0500 Subject: [PATCH 222/729] patterns --- graph/GuidelinesGraph.md | 41 +++--- graph/Modelling with Facets Pattern.md | 177 ++++++++++++++--------- graph/Modelling with Subtypes Pattern.md | 6 +- graph/PatternDescriptionTemplate.md | 11 +- 4 files changed, 135 insertions(+), 100 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 37edda02..dbc27de0 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -95,10 +95,11 @@ In general API design includes the following steps: - Specify errors -When creating your API contract you will define resources based on the domain model supporting your service and identify interactions based on use cases. Therefore it is essential to understand and document your use cases as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because APIs usually are often simplified to support only customer-facing use cases and obfuscate implementation details. We recommend creating a simple resource diagram, like below, to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. +When creating your API contract you will define resources based on the domain model supporting your service and identify interactions based on use cases. Good API design goes beyond modeling the current state of resources and it is important to plan ahead how API evolves. For this it is essential to understand and document your use cases as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. We recommend creating a simple resource diagram, like below, to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. ![Resource model example](ModelExample.png) After resources are defined it’s time to think about the behavior of your API which can be expressed via HTTP methods and operational resources such as functions and actions. As you think about API behavior you identify a happy path and various exceptions and deviations which will be expressed as errors and represented using HTTP codes and error messages. + At every step of your design you need to consider security, privacy and compliance as an intrinsic components of your API implementation. @@ -217,7 +218,7 @@ Since objects of complex types on Graph don’t have unique identifiers, they ar - + @@ -228,6 +229,14 @@ Since objects of complex types on Graph don’t have unique identifiers, they ar ``` +| Microsoft Graph rules for modeling complex resources | | +|---------------------------------------|------------------------------------------------------------| +| :heavy_check_mark: **MUST** use String type for ID | +| :ballot_box_with_check: **SHOULD** use a primary key composed of a single property | +| :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | +| :heavy_check_mark: **MUST** use a value property in the root object to return a collection | +| :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | +| :warning: **SHOULD NOT** add the property id to a complex type | There are different approaches for designing an API resource model in situations with multiple variants of a common concept. Type Hierarchy, Facets, and Flat bag @@ -250,22 +259,15 @@ of properties are three most often used patterns in Microsoft Graph today: Pattern](./Modelling%20with%20Flat%20Bag%20Pattern.md) The following table shows summary of main qualities for each pattern and will -help to select a pattern preferred for your use case. +help to select an syntactically backward compatible pattern fit for your use case. + +| API qualities\
    Use Cases | Properties and behavior described in metadata | Suited for combinations of properties and behaviors | Simple query construction | Recommended Pattern | +|--------------------------------|-------------------------------------|-----------------------------------|---------------------------|---------------------------------| +| Use Case 1 | yes | no | no | Type hierarchy | +| Use Case 2 | ok | yes | yes |Facets | +| Use Case 3 | no | no | yes | Flat bag | -| API qualities\
    Use Cases | Properties and behavior described in metadata | Suited for combinations of properties and behaviors | Simple query construction | Syntactically backward compatible |Recommended Pattern | -|----------------------|------------------------------------------|----------------------------------------|---------------------------|---------------------------------|---------------------------| -| Use Case 1 | yes | no | no | yes |Type hierarchy | -| Use Case 2 | ok | yes | yes | yes |Facets | -| Use Case 3 | no | no | yes | yes |Flat bag | -| Additional Microsoft Graph rules for modeling resources | -|----------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use String type for ID | -| :ballot_box_with_check: **SHOULD** use a primary key composed of a single property | -| :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | -| :heavy_check_mark: **MUST** use a value property in the root object to return a collection | -| :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | -| :warning: **SHOULD NOT** add the property id to a complex type | ### Behavior Modeling @@ -426,11 +428,10 @@ Recommended API Design patterns: | Pattern | Description | Reference | |-------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| -| Key Property | The ability to uniquely identify an object through the key | [Key Property](./evolvable-enums.md) | -| Entity Type | | TBD | -| Complex Type | | TBD | -| Shared Type | The ability to reuse a type defined by another service. | TBD | TBD | +| Addressable Entity | The ability to uniquely identify an object through the key | TBD | +| Shared Type | The ability to reuse a type defined by another service. | TBD | | Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Modeling with Subtypes](./Modelling%20with%20Subtypes%20Pattern.md) | +| Facets | The ability to model parent-child relationships using Facet pattern. | [Modeling with Facets](./Modelling%20with%20Subtypes%20Pattern.md) | | Dictionary | The ability to persist a variable number of properties. | TBD | | Evolvable Enums | The ability to enable non-breaking changes for Enum type. | TBD | | Type Namespace | The ability to reduce the need to prefix types with a qualifier to ensure uniqueness. | TBD | diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md index e9624717..841d2662 100644 --- a/graph/Modelling with Facets Pattern.md +++ b/graph/Modelling with Facets Pattern.md @@ -2,49 +2,26 @@ Microsoft Graph API Design Pattern -## *A frequent pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with common properties and one facet property (of complex type) per variant.* +### *A frequent pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with common properties and facets for variants.* -## Context - -Let’s assume you need to create an API to manage documents, pictures, files of -different formats which are organized in different folder hierarchies across -multiple local and shared drives. These items have many common properties such -as Name, Owner, Creation Date, common relationships like activities and -subscriptions, and a common set of behaviors like CRUD operations and sharing. - -There are also subsets of values that are specific for each variant, for example -hashes and Mime type for files and eight different properties for photo like -camera model and settings. - -Since usually individual users or organizations deal with a vast amount of -information stored in files there is a need for easy filtering and querying -information based on its metadata. - -While modeling for existing requirements we need to create a flexible API design -to be able accommodate future needs like new metadata or behavior. ## Problem - -How to model files and folders as API resources to be able to easily mange them, -query and filter using metadata, and - -A more general problem is how to model a collection of heterogeneous elements -that have a set of common properties and behaviors, and some unique properties -for each variant. +API developer needs to model a set of heterogeneous resources that have common properties and behaviors, and may express features of multiple variants at a time. +For example a movie clip stored on OneDrive has properties of File Type and Video Type. ## Solution -OData allows us to design collections of entities (entity sets, multi valued -navigation properties) with values of different types using **type hierarchy**, -where there is one abstract base type with a few common properties and one -sub-type for each variant of the entity. In the current version of Microsoft -Graph there are many collections of items that represent slightly different -things, variants of one concept. +API designers can create multiple complex types to bundle properties for each variant then define a parent entity type with common properties and one property of a complex type per variant. +In this solution a child variant is identified by a presence of one or multiple facets in the parent object. ## Issues and Considerations When introducing a new subtype, you need to ensure that the new subtype doesn't -change the semantic of the type hierarchy with it's implicit constraints. +change the semantic of the type hierarchy with it's implicit constraints. For example + +When introducing a new subtype to the hierarchy, developers need to ensure that +the new subtype doesn't change the semantic of the type hierarchy with its +implicit constraints. There are a **few potential risks** for client applications when new sub-types are introduced: @@ -56,29 +33,21 @@ are introduced: - Client libraries for strongly typed language might ignore some of the values in the @odata.type property without further configuration and need to be - configured to be able to pick the right (client) type to deserialize into. + updated to be able to pick the right (client) type to deserialize into. -To minimize impact on clients type hierarchy can be refined by annotating the -collections with OData derived type constraints (see validation vocabulary). -This annotation restricts the values to certain sub-trees of an inheritance -hierarchy. It makes it very explicit that the collection only contains elements -of some of the subtypes and helps to not return objects of a type that is -semantically not suitable. In addition, you can follow some of the mitigation -techniques such as: +In addition, you can follow some of the mitigation techniques such as: -- Avoid overgeneralized base types - -- Think about roll-out sequence +- Think about gradual roll-out sequence - Consider that Microsoft Graph does not return objects from a workload that has a type that is not configured in current metadata. To avoid inconsistencies, follow a two-step process: - - Introduce the entity type to the Graph metadata but don’t return objects - of the type in any of the heterogeneous collections. + - Introduce the entity type to the Graph metadata but don’t return + objects of the type in any of the heterogeneous collections. - - Enable your workload to return objects of the new type as items of - collection. + - Enable your workload to return objects of the new type as items of + collection. - Allow time for testing @@ -97,33 +66,97 @@ techniques such as: ## When to Use this Pattern -The Type hierarchy pattern is well familiar to OOP developers and well suited -for strongly typed client programming languages. +The facet pattern is useful when ... + make it easier to query resources using OData $filter expression +This pattern is useful with relatively small number of subtypes otherwise the main object become very sparsely populated. There are related patterns to consider such as -[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat +[Type Hierarchy](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat bag of properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). ## Example - -`GET - -` -returns a collection where each element can be a user or a service principal, -and has an additional property @odata.type to show subtype for each variant: - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -{ -    "@odata.context": -"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", -    "value": [ -        { -            "@**odata.type**": "\#**microsoft.graph.user**", -            "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", -            "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" -            // ... -        } -    ] -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +```XML + + + + + + + + + + + + + + + + + + + + + + + + + + + + ... + +``` + + + +```JSON +https://graph.microsoft.com/beta/me/drive/root/children + +Response shortened for readability: + { + "@microsoft.graph.downloadUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/_layouts/15/download.aspx?UniqueId=66428197-fb89-4611-91f7-77e6d03245b4&Translate=false&tempauth=eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.J1c2VQZXJzaXN0ZW50Q29va2llIjpudWxsLCJpcGFkZHIiOiIyMC4xOTAuMTM1LjQzIn0.SU9ZM2FCa2xaM2UyaC85d0hUNmN4bmU2cEJDZGdncEdtQ0FmM0llR0tUbz0&ApiVersion=2.0", + "createdDateTime": "2021-12-15T00:07:36Z", + "eTag": "\"{66428197-FB89-4611-91F7-77E6D03245B4},2\"", + "id": "01XXNRXFEXQFBGNCP3CFDJD53X43IDERNU", + "lastModifiedDateTime": "2021-12-15T00:07:36Z", + "name": "Versioning and Deprecation.docx", + "webUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/_layouts/15/Doc.aspx?sourcedoc=%7B66428197-FB89-4611-91F7-77E6D03245B4%7D&file=Versioning%20and%20Deprecation.docx&action=default&mobileredirect=true", + "cTag": "\"c:{66428197-FB89-4611-91F7-77E6D03245B4},1\"", + "size": 21400, + ... + "file": { + "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "hashes": { + "quickXorHash": "r2d9uZilW0zEIXwycymsUQzhV+U=" + } + }, + "fileSystemInfo": { + "createdDateTime": "2021-12-15T00:07:36Z", + "lastModifiedDateTime": "2021-12-15T00:07:36Z" + } + }, + { + "@microsoft.graph.downloadUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/_layouts/15/download.aspx?UniqueId=d13e47ea-a6db-4d30-affd-e82dbf01cb51&Translate=false&tempauth=eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.J1c2VQZXJzaXN0ZW50Q29va2llIjpudWxsLCJpcGFkZHIiOiIyMC4xOTAuMTM1LjQzIn0.TWdMOUhoNDlvSEN5UHM5S3VoMms3Nk9IRldPVWJzSDBlb0xRV3Vld244bz0&ApiVersion=2.0", + "createdDateTime": "2021-12-21T16:32:51Z", + "eTag": "\"{D13E47EA-A6DB-4D30-AFFD-E82DBF01CB51},1\"", + "id": "01XXNRXFHKI47NDW5GGBG277PIFW7QDS2R", + "lastModifiedDateTime": "2021-12-21T16:32:51Z", + "name": "WhaleShark.jpg", + "webUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/Documents/WhaleShark.jpg", + "cTag": "\"c:{D13E47EA-A6DB-4D30-AFFD-E82DBF01CB51},1\"", + "size": 29097, + ..... + "file": { + "mimeType": "image/jpeg", + "hashes": { + "quickXorHash": "2vHpAA7RDZJteIwl1pXR980xuh4=" + } + }, + "fileSystemInfo": { + "createdDateTime": "2021-12-21T16:32:51Z", + "lastModifiedDateTime": "2021-12-21T16:32:51Z" + }, + "image": {} + }, +``` diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index a0a71b39..b69eb8c9 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -4,7 +4,7 @@ Microsoft Graph API Design Pattern “Not supposed to be precise but easy to understand” -## *A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* +### *A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* ## Problem @@ -35,11 +35,11 @@ are introduced: - Client libraries for strongly typed language might ignore some of the values in the @odata.type property without further configuration and need to be - configured to be able to pick the right (client) type to deserialize into. + updated to be able to pick the right (client) type to deserialize into. In addition, you can follow some of the mitigation techniques such as: -- Think about roll-out sequence +- Think about gradual roll-out sequence - Consider that Microsoft Graph does not return objects from a workload that has a type that is not configured in current metadata. To avoid diff --git a/graph/PatternDescriptionTemplate.md b/graph/PatternDescriptionTemplate.md index 2ae4f552..12ca1570 100644 --- a/graph/PatternDescriptionTemplate.md +++ b/graph/PatternDescriptionTemplate.md @@ -4,9 +4,9 @@ Microsoft Graph API Design Pattern   -*Provide a short description of the pattern.* - +### *Provide a short description of the pattern.* +
    ## Problem -------- *Describe business context relevant for the pattern.* @@ -21,6 +21,7 @@ Microsoft Graph API Design Pattern *Describe related patterns.* * * + ## When to Use this Pattern ------------------------ @@ -33,7 +34,7 @@ Microsoft Graph API Design Pattern *Describe tradeoffs of the solution.* -  + * * ## Example @@ -43,6 +44,6 @@ Microsoft Graph API Design Pattern * *  -  -  + + From 7a6f8e9e6d1b7ae77f910dd7663ef9a01b5145dc Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 21 Dec 2021 13:56:38 -0500 Subject: [PATCH 223/729] facets pattern --- graph/Modelling with Facets Pattern.md | 120 ++++++++--------------- graph/Modelling with Subtypes Pattern.md | 2 +- 2 files changed, 42 insertions(+), 80 deletions(-) diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md index 841d2662..6600fa6d 100644 --- a/graph/Modelling with Facets Pattern.md +++ b/graph/Modelling with Facets Pattern.md @@ -6,69 +6,23 @@ Microsoft Graph API Design Pattern ## Problem -API developer needs to model a set of heterogeneous resources that have common properties and behaviors, and may express features of multiple variants at a time. -For example a movie clip stored on OneDrive has properties of File Type and Video Type. +API designer needs to model a set of heterogeneous resources that have common properties and behaviors, and may express features of multiple variants at a time because variants are not mutually exclusive. +For example a movie clip stored on OneDrive is a file and have additional properties for the video variant. ## Solution -API designers can create multiple complex types to bundle properties for each variant then define a parent entity type with common properties and one property of a complex type per variant. +API designers creates multiple complex types to bundle properties for each variant then define entity type with common properties across variants and one property of complex type per variant. In this solution a child variant is identified by a presence of one or multiple facets in the parent object. ## Issues and Considerations When introducing a new subtype, you need to ensure that the new subtype doesn't -change the semantic of the type hierarchy with it's implicit constraints. For example - -When introducing a new subtype to the hierarchy, developers need to ensure that -the new subtype doesn't change the semantic of the type hierarchy with its -implicit constraints. - -There are a **few potential risks** for client applications when new sub-types -are introduced: - -- De-serialization code might break because of missing properties in returned - collection items. Even though property X was mandatory on all subtypes - previously returned, the new subtype might not have this property and the - client code needs to deal with that. - -- Client libraries for strongly typed language might ignore some of the values - in the @odata.type property without further configuration and need to be - updated to be able to pick the right (client) type to deserialize into. - -In addition, you can follow some of the mitigation techniques such as: - -- Think about gradual roll-out sequence - - - Consider that Microsoft Graph does not return objects from a workload - that has a type that is not configured in current metadata. To avoid - inconsistencies, follow a two-step process: - - - Introduce the entity type to the Graph metadata but don’t return - objects of the type in any of the heterogeneous collections. - - - Enable your workload to return objects of the new type as items of - collection. - -- Allow time for testing - - - Inform the clients about the change and allow them to test the changes - in beta. Time is required to implement the code necessary to deal with - the new entity type, both in terms of de-serialization as well as - integrating it into the rest of the application. - -- Communicate the change in semantics - - - It is necessary for the client developers to incorporate the new - semantic into their application/service, even if the change is perceived - to be small. This requires early communication and clear documentation - of what the new type represents and why/how it is considered a subtype - of the original abstract type of the collection. +change the semantic of the type hierarchy with it's implicit constraints. +This pattern may not work well for many mutually exclusive variants because the main entity type will be sparcely populated and may become difficult to reason about. ## When to Use this Pattern -The facet pattern is useful when ... - make it easier to query resources using OData $filter expression -This pattern is useful with relatively small number of subtypes otherwise the main object become very sparsely populated. +The facet pattern is useful when there is a small number of variants and they are not mutually exclusive. It also makes syntactically easier to query resources using OData $filter expression since it doesn't require casting There are related patterns to consider such as [Type Hierarchy](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat @@ -76,8 +30,11 @@ bag of properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). ## Example +The driveItem resource represents a file, folder,image or other item stored in a drive and is modeled using entity type with multiple facets. + ```XML - + + @@ -108,55 +65,60 @@ properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). ``` +API request to get all items from a personal OneDrive will return a collection of items with different facets populated. In the example below there is a folder,a file and an image in the collection. The imageentity has two facets populated: file and image. - -```JSON +``` https://graph.microsoft.com/beta/me/drive/root/children Response shortened for readability: - { - "@microsoft.graph.downloadUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/_layouts/15/download.aspx?UniqueId=66428197-fb89-4611-91f7-77e6d03245b4&Translate=false&tempauth=eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.J1c2VQZXJzaXN0ZW50Q29va2llIjpudWxsLCJpcGFkZHIiOiIyMC4xOTAuMTM1LjQzIn0.SU9ZM2FCa2xaM2UyaC85d0hUNmN4bmU2cEJDZGdncEdtQ0FmM0llR0tUbz0&ApiVersion=2.0", + + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('93816c1c-1b19-41de-a322-a1643d7f4d39')/drive/root/children", + "value": [ + { + "createdDateTime": "2021-07-07T13:59:47Z", + "eTag": "\"{2A158A2F-07E6-4F8F-A000-24C379D0CEE7},1\"", + "id": "01XXNRXFBPRIKSVZQHXXXXXABEYN45BTXH", + "lastModifiedDateTime": "2021-07-07T13:59:47Z", + "name": "Microsoft Teams Chat Files", + ..., + "folder": { + "childCount": 15 + } + }, + ... + { + "@microsoft.graph.downloadUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/_layouts/15/download.SU9ZM2FCa2xaM2UyaC85d0hUNmN4bmU2cEJDZGdncEdtQ0FmM0llR0tUbz0&ApiVersion=2.0", "createdDateTime": "2021-12-15T00:07:36Z", "eTag": "\"{66428197-FB89-4611-91F7-77E6D03245B4},2\"", - "id": "01XXNRXFEXQFBGNCP3CFDJD53X43IDERNU", + "id": "01XXNRXFEXQFBGNCP3CFDXXXXX43IDERNU", "lastModifiedDateTime": "2021-12-15T00:07:36Z", - "name": "Versioning and Deprecation.docx", - "webUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/_layouts/15/Doc.aspx?sourcedoc=%7B66428197-FB89-4611-91F7-77E6D03245B4%7D&file=Versioning%20and%20Deprecation.docx&action=default&mobileredirect=true", - "cTag": "\"c:{66428197-FB89-4611-91F7-77E6D03245B4},1\"", - "size": 21400, - ... + "name": "Versioning and Deprecation.docx", + ..., "file": { "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "hashes": { "quickXorHash": "r2d9uZilW0zEIXwycymsUQzhV+U=" } }, - "fileSystemInfo": { - "createdDateTime": "2021-12-15T00:07:36Z", - "lastModifiedDateTime": "2021-12-15T00:07:36Z" - } + ... }, { - "@microsoft.graph.downloadUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/_layouts/15/download.aspx?UniqueId=d13e47ea-a6db-4d30-affd-e82dbf01cb51&Translate=false&tempauth=eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.J1c2VQZXJzaXN0ZW50Q29va2llIjpudWxsLCJpcGFkZHIiOiIyMC4xOTAuMTM1LjQzIn0.TWdMOUhoNDlvSEN5UHM5S3VoMms3Nk9IRldPVWJzSDBlb0xRV3Vld244bz0&ApiVersion=2.0", + "@microsoft.graph.downloadUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/_layouts/15/download.TWdMOUhoNDlvSEN5UHM5S3VoMms3Nk9IRldPVWJzSDBlb0xRV3Vld244bz0&ApiVersion=2.0", "createdDateTime": "2021-12-21T16:32:51Z", - "eTag": "\"{D13E47EA-A6DB-4D30-AFFD-E82DBF01CB51},1\"", - "id": "01XXNRXFHKI47NDW5GGBG277PIFW7QDS2R", + "eTag": "\"{"01XXNRXFBPRIKSVZQHXXXXXABEYN45BTXH", +},1\"", + "id": "01XXNRXFHKI47NDW5GGXXX77PIFW7QDS2R", "lastModifiedDateTime": "2021-12-21T16:32:51Z", "name": "WhaleShark.jpg", - "webUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/Documents/WhaleShark.jpg", - "cTag": "\"c:{D13E47EA-A6DB-4D30-AFFD-E82DBF01CB51},1\"", - "size": 29097, - ..... + ... "file": { "mimeType": "image/jpeg", "hashes": { "quickXorHash": "2vHpAA7RDZJteIwl1pXR980xuh4=" } }, - "fileSystemInfo": { - "createdDateTime": "2021-12-21T16:32:51Z", - "lastModifiedDateTime": "2021-12-21T16:32:51Z" - }, + ..., "image": {} - }, + } + ] ``` diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index b69eb8c9..d1c814d8 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -113,7 +113,7 @@ API request to get members of a group returns a heterogeneous collection of users and groups where each element can be a user or a group, and has an additional property @odata.type for a variant subtype: -```JSON +``` GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members?$select=id,displayName … Response payload: From 3f3407a4addc202651e8cc5f945006ab735ac9c3 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 21 Dec 2021 13:59:26 -0500 Subject: [PATCH 224/729] example --- graph/Modelling with Facets Pattern.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md index 6600fa6d..e138605b 100644 --- a/graph/Modelling with Facets Pattern.md +++ b/graph/Modelling with Facets Pattern.md @@ -76,9 +76,6 @@ Response shortened for readability: "value": [ { "createdDateTime": "2021-07-07T13:59:47Z", - "eTag": "\"{2A158A2F-07E6-4F8F-A000-24C379D0CEE7},1\"", - "id": "01XXNRXFBPRIKSVZQHXXXXXABEYN45BTXH", - "lastModifiedDateTime": "2021-07-07T13:59:47Z", "name": "Microsoft Teams Chat Files", ..., "folder": { @@ -87,11 +84,7 @@ Response shortened for readability: }, ... { - "@microsoft.graph.downloadUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/_layouts/15/download.SU9ZM2FCa2xaM2UyaC85d0hUNmN4bmU2cEJDZGdncEdtQ0FmM0llR0tUbz0&ApiVersion=2.0", "createdDateTime": "2021-12-15T00:07:36Z", - "eTag": "\"{66428197-FB89-4611-91F7-77E6D03245B4},2\"", - "id": "01XXNRXFEXQFBGNCP3CFDXXXXX43IDERNU", - "lastModifiedDateTime": "2021-12-15T00:07:36Z", "name": "Versioning and Deprecation.docx", ..., "file": { @@ -103,12 +96,7 @@ Response shortened for readability: ... }, { - "@microsoft.graph.downloadUrl": "https://microsoft-my.sharepoint-df.com/personal/opodolyako_microsoft_com/_layouts/15/download.TWdMOUhoNDlvSEN5UHM5S3VoMms3Nk9IRldPVWJzSDBlb0xRV3Vld244bz0&ApiVersion=2.0", "createdDateTime": "2021-12-21T16:32:51Z", - "eTag": "\"{"01XXNRXFBPRIKSVZQHXXXXXABEYN45BTXH", -},1\"", - "id": "01XXNRXFHKI47NDW5GGXXX77PIFW7QDS2R", - "lastModifiedDateTime": "2021-12-21T16:32:51Z", "name": "WhaleShark.jpg", ... "file": { From 8d0ce2875b04235efc8d95e09fb180288b54789c Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 21 Dec 2021 17:49:13 -0500 Subject: [PATCH 225/729] examples --- graph/Modelling with Facets Pattern.md | 2 +- graph/Modelling with Subtypes Pattern.md | 68 ++++++++++-------------- 2 files changed, 29 insertions(+), 41 deletions(-) diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md index e138605b..7e03e2b9 100644 --- a/graph/Modelling with Facets Pattern.md +++ b/graph/Modelling with Facets Pattern.md @@ -65,7 +65,7 @@ The driveItem resource represents a file, folder,image or other item stored in a ``` -API request to get all items from a personal OneDrive will return a collection of items with different facets populated. In the example below there is a folder,a file and an image in the collection. The imageentity has two facets populated: file and image. +API request to get all items from a personal OneDrive will return a hetereogenius collection with different facets populated. In the example below there is a folder,a file and an image in the collection. The image entity has two facets populated: file and image. ``` https://graph.microsoft.com/beta/me/drive/root/children diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index d1c814d8..919a068d 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -17,54 +17,24 @@ of new variants without breaking changes. API designers can use OData **type hierarchy**, where there is one abstract base type with a few shared properties representing the common concept and one -sub-type for each variant of the entity. +sub-type for each variant of the entity. In hierarchy, the interdependencies of properties, i.e. which properties are relevant for which variants, is fully captured in metadata and client code can potentially leverage that to construct and/or validate requests. ## Issues and Considerations When introducing a new subtype to the hierarchy, developers need to ensure that the new subtype doesn't change the semantic of the type hierarchy with its implicit constraints. +To retrieve properties specific for a derived type an API request URL may need to include casting to the derived type. If type hierarchy is very deep then resulting URL may become very long and not easily readable. -There are a **few potential risks** for client applications when new sub-types +There are a few consideration to take into account when new sub-types are introduced: -- De-serialization code might break because of missing properties in returned - collection items. Even though property X was mandatory on all subtypes - previously returned, the new subtype might not have this property and the - client code needs to deal with that. - -- Client libraries for strongly typed language might ignore some of the values +- TODO add something about SDK dependencies and required actions +- TODO Client libraries for strongly typed language might ignore some of the values in the @odata.type property without further configuration and need to be updated to be able to pick the right (client) type to deserialize into. +TODO something about In addition, you can follow some of the mitigation techniques such as: -In addition, you can follow some of the mitigation techniques such as: - -- Think about gradual roll-out sequence - - - Consider that Microsoft Graph does not return objects from a workload - that has a type that is not configured in current metadata. To avoid - inconsistencies, follow a two-step process: - - - Introduce the entity type to the Graph metadata but don’t return - objects of the type in any of the heterogeneous collections. - - - Enable your workload to return objects of the new type as items of - collection. - -- Allow time for testing - - - Inform the clients about the change and allow them to test the changes - in beta. Time is required to implement the code necessary to deal with - the new entity type, both in terms of de-serialization as well as - integrating it into the rest of the application. - -- Communicate the change in semantics - - - It is necessary for the client developers to incorporate the new - semantic into their application/service, even if the change is perceived - to be small. This requires early communication and clear documentation - of what the new type represents and why/how it is considered a subtype - of the original abstract type of the collection. ## When to Use this Pattern @@ -81,7 +51,7 @@ properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). The directoryObject type is the main abstraction for many directory types such as users, organizational contacts, devices, service principals -and groups stored in Azure Active Directory. Since any directoryObject object is a unique entity the directoryObject type itself is derived from the graph.entity base type. +and groups stored in Azure Active Directory. Since any a directoryObject object is a unique entity, the directoryObject type itself is derived from the graph.entity base type. ```XML @@ -115,8 +85,8 @@ additional property @odata.type for a variant subtype: ``` GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members?$select=id,displayName -… -Response payload: + +Response payload shortened for readability: { "@odata.context": @@ -132,7 +102,25 @@ Response payload: "id": "45f25951-d04f-4c44-b9b0-2a79e915658d", "displayName": "Department 456" }, -… + ...     ] } ``` +API request for a subtype specific property requires type casting to the subtype, i.e. to retrieve jobTitle property, enabled for the user type, you need to cast from the directoryObject collection items to the microsoft.graph.group derived type. + +``` +GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members/microsoft.graph.user?$select=displayName,jobTitle + +Response payload shortened for readability: + +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(displayName,jobTitle)", + "value": [ + { + "displayName": "John Cob", + "jobTitle": "RESEARCHER II" + }, + ... + ] +} +``` \ No newline at end of file From 3845a39e3a1c8a750a481cc14accad45cc80f62f Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 22 Dec 2021 10:49:04 -0500 Subject: [PATCH 226/729] diagram update --- graph/ModelExample.png | Bin 66461 -> 49988 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/graph/ModelExample.png b/graph/ModelExample.png index c8481778f1df97a6284a5b64c581dae3fd3300a0..450f4f878887e733569273d210916cb177737cd1 100644 GIT binary patch literal 49988 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 literal 66461 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 z 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 Date: Wed, 22 Dec 2021 12:54:23 -0500 Subject: [PATCH 227/729] Update graph/Modelling with Facets Pattern.md Co-authored-by: Darrel --- graph/Modelling with Facets Pattern.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md index 7e03e2b9..68579220 100644 --- a/graph/Modelling with Facets Pattern.md +++ b/graph/Modelling with Facets Pattern.md @@ -7,7 +7,7 @@ Microsoft Graph API Design Pattern ## Problem API designer needs to model a set of heterogeneous resources that have common properties and behaviors, and may express features of multiple variants at a time because variants are not mutually exclusive. -For example a movie clip stored on OneDrive is a file and have additional properties for the video variant. +For example a movie clip stored on OneDrive is both a file and a video. There are properties associated to each variant. ## Solution From 6b10d5e8c7b4758141283f090b1bc31e19091fa1 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 22 Dec 2021 12:55:34 -0500 Subject: [PATCH 228/729] Update graph/Modelling with Facets Pattern.md Co-authored-by: Darrel --- graph/Modelling with Facets Pattern.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md index 68579220..879b23c2 100644 --- a/graph/Modelling with Facets Pattern.md +++ b/graph/Modelling with Facets Pattern.md @@ -11,7 +11,7 @@ For example a movie clip stored on OneDrive is both a file and a video. There ar ## Solution -API designers creates multiple complex types to bundle properties for each variant then define entity type with common properties across variants and one property of complex type per variant. +API designers create multiple complex types to bundle properties for each variant then define an entity type with a property for each complex type to hold the properties of the variant. In this solution a child variant is identified by a presence of one or multiple facets in the parent object. ## Issues and Considerations From 90c298a3d68068ea35a500da5349e59b83d259b3 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 22 Dec 2021 12:56:29 -0500 Subject: [PATCH 229/729] Update graph/Modelling with Facets Pattern.md Co-authored-by: Darrel --- graph/Modelling with Facets Pattern.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md index 879b23c2..2813638e 100644 --- a/graph/Modelling with Facets Pattern.md +++ b/graph/Modelling with Facets Pattern.md @@ -12,7 +12,7 @@ For example a movie clip stored on OneDrive is both a file and a video. There ar ## Solution API designers create multiple complex types to bundle properties for each variant then define an entity type with a property for each complex type to hold the properties of the variant. -In this solution a child variant is identified by a presence of one or multiple facets in the parent object. +In this solution a child variant is identified by a presence of one or more facets in the parent object. ## Issues and Considerations From 11b36cd571ea45cf9fa5ab9c29f8c4bd93aea574 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 22 Dec 2021 12:56:37 -0500 Subject: [PATCH 230/729] Update graph/Modelling with Facets Pattern.md Co-authored-by: Darrel --- graph/Modelling with Facets Pattern.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md index 2813638e..d7854880 100644 --- a/graph/Modelling with Facets Pattern.md +++ b/graph/Modelling with Facets Pattern.md @@ -65,7 +65,7 @@ The driveItem resource represents a file, folder,image or other item stored in a ``` -API request to get all items from a personal OneDrive will return a hetereogenius collection with different facets populated. In the example below there is a folder,a file and an image in the collection. The image entity has two facets populated: file and image. +API request to get all items from a personal OneDrive will return a heterogenous collection with different facets populated. In the example below there is a folder, a file and an image in the collection. The image entity has two facets populated: file and image. ``` https://graph.microsoft.com/beta/me/drive/root/children From 5b711e95df9990066afb8111338261703a1fb506 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 22 Dec 2021 14:06:52 -0500 Subject: [PATCH 231/729] Update graph/GuidelinesGraph.md Co-authored-by: Darrel --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index dbc27de0..3ecd873e 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -32,7 +32,7 @@ Table of Contents When building a digital ecosystem API usability becomes a business priority. Success of your ecosystem depends on APIs that are easy to discover, simple to use, fit to purpose, and consistent across your products. -This document offers guidance that Graph API developer teams MUST follow to +This document offers guidance that Microsoft Graph API developer teams MUST follow to ensure that Microsoft Graph has a consistent and easy to use API surface. A new API design should meet the following goals: From 627bb58656aec77e50ebaef2c58d830447b41b41 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 22 Dec 2021 14:10:40 -0500 Subject: [PATCH 232/729] removed Odata from breaking changes list --- graph/GuidelinesGraph.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index dbc27de0..12088def 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -396,8 +396,6 @@ breaking change. - Addition of a Nullable="false" parameters to existing actions and functions - **Adding attributes to existing nodes is considered breaking??** -For the full list of rules you can refer to [this section of the OData V4 -spec](https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209). ### Versioning and Deprecation As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If it is not possible then you must version elements of your APIs. From 178598feb8cf4f63583a5f52a46c722d8a8730b2 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 22 Dec 2021 14:42:01 -0500 Subject: [PATCH 233/729] fix for complex types --- graph/GuidelinesGraph.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 79bdc093..e8dd734d 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -105,7 +105,7 @@ At every step of your design you need to consider security, privacy and complian ### Naming -API resources are typically described by nouns. Resource and properties names appear in API URLs and payloads and must be descriptive and easy to understand for developers. Ease of understanding comes from familiarity and recognition therefore when thinking about naming you should consider consistency with industry standards, product namespace and other Graph APIs. Microsoft Graph naming conventions follow Microsoft REST API Guidelines. +API resources are typically described by nouns. Resource and properties names appear in API URLs and payloads and must be descriptive and easy to understand for developers. Ease of understanding comes from familiarity and recognition therefore when thinking about naming you should consider consistency with **??industry standards, product namespace and other Graph APIs**. Microsoft Graph naming conventions follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). Below is a short summary of the most often used conventions. @@ -168,7 +168,7 @@ groupings: 2. A Microsoft *product or service offerings* covering multiple use cases, i.e. /teamwork, /directory. -3. A *feature* offering covering a single use case and *shared* across multiple +3. A *feature offering* covering a single use case and *shared* across multiple Microsoft products, i.e. /search, /notifications, /subscriptions, /files. 4. *Administrative configuration* functions for specific products. (Note: this @@ -210,8 +210,8 @@ Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conv ### Resource Modeling Patterns -You can model complex resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity types declare a key property to uniquely identify its objects and Complex Types don't. In Microsoft Graph this key property has "id" as a prescribed name. -Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must use Entity Type to model addressable resources. Complex types are better suited to represent composite properties of API entities. +You can model complex resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity type declares a key property to uniquely identify its objects and Complex Type does not. In Microsoft Graph this key property has "id" as a prescribed name. +Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources. Complex types are better suited to represent composite properties of API entities. ```XML From b3c0f0f3b53f73aaff5be38727ebf214f1fb2556 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 22 Dec 2021 14:50:25 -0500 Subject: [PATCH 234/729] versionsning --- graph/GuidelinesGraph.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e8dd734d..5b34cc07 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -399,11 +399,8 @@ breaking change. ### Versioning and Deprecation As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If it is not possible then you must version elements of your APIs. -Microsoft Graph allows versioning of elements including entities and properties. The versioning process goes along with deprecation and as soon as you introduce a new element update the previous version needs to follow the deprecation process before retirement. You must create a new version when: - - Renaming any element of your API or - - Restructuring a resource type. - -If the current element name is best, a new property is added that has the existing name plus the suffix _v2, indicating that it is the successor to the original property. The original property is then marked as deprecated using annotations. +Microsoft Graph allows versioning of elements including entities and properties. The versioning process goes along with deprecation and as soon as you introduce a new element update the previous version needs to follow the deprecation process. You must create a new version of your element for any breaking change and name it uniquely. +If the current element name is best, a new element is added that has the existing name plus the suffix _v2, indicating that it is the successor to the original. The original element is then marked as deprecated using annotations. Microsoft Graph provides two public endpoints to support API lifecycle: 1. API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in general availability (GA) status. From f9ec595758c72c67e455a65dcccdadc9af6161fc Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 22 Dec 2021 18:13:14 -0500 Subject: [PATCH 235/729] updates --- graph/GuidelinesGraph.md | 25 ++++++++++++------------ graph/Modelling with Facets Pattern.md | 7 +++---- graph/Modelling with Subtypes Pattern.md | 6 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 5b34cc07..030f4652 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -105,7 +105,7 @@ At every step of your design you need to consider security, privacy and complian ### Naming -API resources are typically described by nouns. Resource and properties names appear in API URLs and payloads and must be descriptive and easy to understand for developers. Ease of understanding comes from familiarity and recognition therefore when thinking about naming you should consider consistency with **??industry standards, product namespace and other Graph APIs**. Microsoft Graph naming conventions follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). +API resources are typically described by nouns. Resource and properties names appear in API URLs and payloads and must be descriptive and easy to understand for developers. Ease of understanding comes from familiarity and recognition therefore when thinking about naming you should consider consistency with other Graph APIs, industry standards,and names in the product user interface. Microsoft Graph naming conventions follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). Below is a short summary of the most often used conventions. @@ -373,33 +373,36 @@ breaking change. \*\* Non-breaking changes:\*\* -- Addition of an annotation OpenType="true" + - Addition of properties that are nullable or have a default value - Addition of a member to an evolvable enumeration -- Removal, rename, or change to the type of an open extension -- Removal, rename, or change to the type of an annotation -- Introduction of paging to existing collections -- **Changes to error codes?????** +- Removal, rename, or change to the type of an annotation - Changes to the order of properties - Changes to the length or format of opaque strings, such as resource IDs +- Addition or removal of an annotation OpenType="true" \*\* Breaking changes:\*\* - Changes to the URL or fundamental request/response associated with a resource -- Changing semantics of resource representation - Removal, rename, or change to the type of a declared property - Removal or rename of APIs or API parameters - Addition of a required request header -- Addition of a EnumType members for non-extensible enumerations +- Addition of a EnumType members for non-evolvable enumerations - Addition of a Nullable="false" properties to existing types - Addition of a Nullable="false" parameters to existing actions and functions -- **Adding attributes to existing nodes is considered breaking??** +- Changes to top-level error codes +- Introduction of paging to existing collections +- Changes to the default order of collection elements +- Significant changes to the performance of APIs such as increased latency, rate limits or concurrency. + ### Versioning and Deprecation As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If it is not possible then you must version elements of your APIs. -Microsoft Graph allows versioning of elements including entities and properties. The versioning process goes along with deprecation and as soon as you introduce a new element update the previous version needs to follow the deprecation process. You must create a new version of your element for any breaking change and name it uniquely. +Microsoft Graph allows versioning of elements including entities and properties. The versioning process goes along with deprecation and as soon as you introduce a new element update the previous version needs to follow the deprecation process. + +You must create a new version of your element for any breaking change and name it uniquely. If the current element name is best, a new element is added that has the existing name plus the suffix _v2, indicating that it is the successor to the original. The original element is then marked as deprecated using annotations. Microsoft Graph provides two public endpoints to support API lifecycle: @@ -423,13 +426,11 @@ Recommended API Design patterns: | Pattern | Description | Reference | |-------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| -| Addressable Entity | The ability to uniquely identify an object through the key | TBD | | Shared Type | The ability to reuse a type defined by another service. | TBD | | Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Modeling with Subtypes](./Modelling%20with%20Subtypes%20Pattern.md) | | Facets | The ability to model parent-child relationships using Facet pattern. | [Modeling with Facets](./Modelling%20with%20Subtypes%20Pattern.md) | | Dictionary | The ability to persist a variable number of properties. | TBD | | Evolvable Enums | The ability to enable non-breaking changes for Enum type. | TBD | -| Type Namespace | The ability to reduce the need to prefix types with a qualifier to ensure uniqueness. | TBD | | Change Tracking | The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph | TBD | | Long Running Operations | The ability to model asynchronous operations. | TBD | | Delta Queries | The ability to query changes in the data exposed by Microsoft Graph | TBD | diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md index d7854880..30a3569b 100644 --- a/graph/Modelling with Facets Pattern.md +++ b/graph/Modelling with Facets Pattern.md @@ -16,13 +16,12 @@ In this solution a child variant is identified by a presence of one or more face ## Issues and Considerations -When introducing a new subtype, you need to ensure that the new subtype doesn't -change the semantic of the type hierarchy with it's implicit constraints. -This pattern may not work well for many mutually exclusive variants because the main entity type will be sparcely populated and may become difficult to reason about. +When introducing a new facet, you need to ensure that the new facet doesn't change the semantic of the model with it's implicit constraints. + ## When to Use this Pattern -The facet pattern is useful when there is a small number of variants and they are not mutually exclusive. It also makes syntactically easier to query resources using OData $filter expression since it doesn't require casting +The facet pattern is useful when there is a number of variants and they are not mutually exclusive. It also makes syntactically easier to query resources using OData $filter expression since it doesn't require casting. There are related patterns to consider such as [Type Hierarchy](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index 919a068d..7397b1ae 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -2,14 +2,13 @@ Microsoft Graph API Design Pattern -“Not supposed to be precise but easy to understand” ### *A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* ## Problem The API design requires to model a set of entities based on a common concept -that can be further grouped into mutually exclusive variants with specific +that can be further grouped into **mutually exclusive variants** with specific properties and behaviors. The API design should be evolvable and allow addition of new variants without breaking changes. @@ -33,7 +32,8 @@ are introduced: - TODO Client libraries for strongly typed language might ignore some of the values in the @odata.type property without further configuration and need to be updated to be able to pick the right (client) type to deserialize into. -TODO something about In addition, you can follow some of the mitigation techniques such as: +- In the case of public APIs in GA versions clients may develop their applications to support exclusively the current set of subtypes and don’t expect new variations. To mitigate the risk of clients disruption, when introducing a new subtype, allow ample time for communication and rollout. + ## When to Use this Pattern From 3016a99195dc5516f87c760682ab0a3da2f60abe Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 22 Dec 2021 18:17:51 -0500 Subject: [PATCH 236/729] added relationships --- graph/GuidelinesGraph.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 030f4652..abdb5585 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -88,6 +88,8 @@ In general API design includes the following steps: - Define your domain model - Derive and name your API resources + +- Describe relationships between resources - Determine required behavior From 738ffd68fd0d8cd6b57d81d817a37203871d91c8 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 22 Dec 2021 19:18:00 -0500 Subject: [PATCH 237/729] added POST example --- graph/Modelling with Subtypes Pattern.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md index 7397b1ae..73dea0a2 100644 --- a/graph/Modelling with Subtypes Pattern.md +++ b/graph/Modelling with Subtypes Pattern.md @@ -123,4 +123,16 @@ Response payload shortened for readability: ... ] } +``` +API request to create a subtype object in a polymorphic collection requires "@odata.type" specified in the request body. +``` +POST https://graph.microsoft.com/v1.0/directoryObjects + +{ + "@odata.type": "#microsoft.graph.group", + "displayName": "Library Assist", + "mailEnabled": false, + "mailNickname": "library", + "securityEnabled": true +} ``` \ No newline at end of file From a92c1b5a32a06ff45ac3652781eda8772f103fdf Mon Sep 17 00:00:00 2001 From: "Johan Stenberg (MSFT)" Date: Mon, 27 Dec 2021 10:17:53 -0800 Subject: [PATCH 238/729] Added description for x-ms-*-request-id Added the intended usage pattern for the azure specific request id values. --- azure/Guidelines.md | 57 +++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index d3b751dc..b098a03b 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -167,25 +167,27 @@ Array | One of a) a comma-separated list of values (preferred), or b) separ The table below lists the headers most used by Azure services: -Header Key | Applies to | Example -------------------- | ---------- | ------------- -_authorization_ | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) -_x-ms-useragent_ | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) -traceparent | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) -tracecontext | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) -accept | Request | application/json -If-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) -If-None-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) -If-Modified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) -If-Unmodified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) -date | Both | Sun, 06 Nov 1994 08:49:37 GMT (see [RFC7231, Section 7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2)) -_content-type_ | Both | application/merge-patch+json -_content-length_ | Both | 1024 -_x-ms-request-id_ | Response | 4227cdc5-9f48-4e84-921a-10967cb785a0 -ETag | Response | "67ab43" (see [Conditional Requests](#Conditional-Requests)) -last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT -_x-ms-error-code_ | Response | (see [Handling Errors](#Handling-Errors)) -retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)) +Header Key | Applies to | Example +----------------------- | ---------- | ------------- +_authorization_ | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) +_x-ms-useragent_ | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) +traceparent | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) +tracecontext | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) +accept | Request | application/json +If-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) +If-None-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) +If-Modified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) +If-Unmodified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) +date | Both | Sun, 06 Nov 1994 08:49:37 GMT (see [RFC7231, Section 7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2)) +_content-type_ | Both | application/merge-patch+json +_content-length_ | Both | 1024 +_x-ms-request-id_ | Response | 4227cdc5-9f48-4e84-921a-10967cb785a0 (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) +_x-ms-client-request-id_| Both | 227cdc5-9f48-4e84-921a-10967cb785a1 (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) +_x-ms-return-client-request-id_| Request | true (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) +ETag | Response | "67ab43" (see [Conditional Requests](#Conditional-Requests)) +last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT +_x-ms-error-code_ | Response | (see [Handling Errors](#Handling-Errors)) +retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)) :white_check_mark: **DO** support all headers shown in _italics_ @@ -767,7 +769,14 @@ In the rare case where a server rolls back a version that clients are already us ### Repeatability of requests -The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. +The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient + + + + + + +uted applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. :ballot_box_with_check: **YOU SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). @@ -993,6 +1002,14 @@ Client libraries are required to send telemetry and distributed tracing informat - [Azure SDK Distributed tracing policy](https://azure.github.io/azure-sdk/general_azurecore.html#distributed-tracing-policy) - [Open Telemetry](https://opentelemetry.io/) +In addition to distributed tracing, Azure also uses a set of common correlation headers: + +|Name |Applies to|Description| +|-----------------------------|----------|-----------| +|x-ms-client-request-id. |Both |Optional. Caller-specified value identifying the request, in the form of a GUID with no decoration such as curly braces (e.g. `x-ms-client-request-id: 9C4D50EE-2D56-4CD3-8152-34347DC9F2B0`). If the caller provides this header the service **must** log this with their traces to facilitate tracing a single request. Because this header can be client-generated, it should not be assumed to be unique by the service implementation. +|x-ms-return-client-request-id|Request. |Optional. If specified, the service **must** include a `x-ms-client-request-id` header with the corresponding value in the response. Allows clients to correlate responses with requests. +|x-ms-request-id |Response |Required. Service generated correlation id identifying the request, in the form of a GUID with no decoratoin such as curly braces. In contrast to the the `x-ms-client-request-id`, the service **must** ensure that this valu is globally unique. Services should log this value with their traces to facilitate tracing of a single request. + ## Final thoughts These guidelines describe the upfront design considerations, technology building blocks, and common patterns that Azure teams encounter when building an API for their service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. From 36845fef6a5611971041f216514081e125aa05b0 Mon Sep 17 00:00:00 2001 From: "Johan Stenberg (MSFT)" Date: Mon, 27 Dec 2021 10:20:49 -0800 Subject: [PATCH 239/729] Fixed typo/ghost edits --- azure/Guidelines.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index b098a03b..f3ab903f 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -769,14 +769,7 @@ In the rare case where a server rolls back a version that clients are already us ### Repeatability of requests -The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient - - - - - - -uted applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. +The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. :ballot_box_with_check: **YOU SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). From fb975ef9f4781edfbf7fbc8b770d7800d2b87615 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 3 Jan 2022 17:51:32 -0500 Subject: [PATCH 240/729] Approach update --- graph/GuidelinesGraph.md | 47 +++++++++++++++------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index abdb5585..a802139f 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -47,15 +47,7 @@ The Microsoft Graph guidelines are an extension of the Microsoft REST API guidelines. Readers are assumed also be reading and following the Microsoft REST API guidelines except where this document outlines specific differences or exceptions to those guidelines. Together these guidelines and a library of API patterns serve as the means by -which API teams discuss and come to consensus on API review recommendations. - -This document borrows from multiple public sources such as: - -1. Microsoft Azure REST API Guidelines - -2. Google Cloud Platform APIs - -3. WSO2 Rest API Design Guidelines and others. +which API teams discuss and come to consensus on API review requirements. Technology and software are constantly changing and evolving, and as such, this is intended to be a living document. [Open an @@ -66,21 +58,19 @@ a change or propose a new idea. This document offers prescriptive guidance labeled as follows: -:heavy_check_mark: **MUST** satisfy this specification. If not following this -advice, you MUST disclose your reason during the Graph API review. +:heavy_check_mark: **MUST** satisfy this specification. + +:no_entry: **MUST NOT** use this pattern. -:no_entry: **MUST NOT** use this pattern. If not following this advice, you MUST -disclose your reason during the Graph API review. +:ballot_box_with_check: **SHOULD** fulfill this specification. -:ballot_box_with_check: **SHOULD** fulfill this specification. If not -following this advice, you MUST disclose your reason during the Graph API -review. +:warning: **SHOULD NOT** adopt this pattern. -:warning: **SHOULD NOT** adopt this pattern. If not following this advice, you MUST disclose your reason during the Graph API review. +If not following these advices, you MUST disclose your reasons during the Graph API review. ## Design Approach -The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote API-first design approach where you begin your product design by focusing on how information will be exchanged and represented and creating an interface contract for your API which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface and is essential for agility, predictability, and reuse of your APIs. Starting with user-facing contracts also promotes good understanding of user interactions, your modeling domain, and understanding of how the service will evolve. Microsoft Graph supports resource and query based API styles which follow HTTP, REST, and JSON standards, where API contract is described using ODATA conventions and schema definition (see Documentation · OData - the Best Way to REST). +The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote API-first design approach where you begin your product design by focusing on how information will be exchanged and represented and creating an interface contract for your API which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface from your implementation and is essential for agility, predictability, and reuse of your APIs. Established interface contract allows developers to use your API while internal teams are still working on implementation, API specifications enable designing of user experience and test cases in parallel. Starting with user-facing contracts also promotes a good understanding of system interactions, your modeling domain, and understanding of how the service will evolve. Microsoft Graph supports resource and query-based API styles which follow HTTP, REST, and JSON standards, where API contract is described using ODATA conventions and schema definition (see Documentation · OData - the Best Way to REST). [Documentation · OData - the Best Way to REST](https://www.odata.org/documentation/)). In general API design includes the following steps: @@ -93,21 +83,21 @@ In general API design includes the following steps: - Determine required behavior -- Determine user roles and permissions +- Determine user roles and application permissions - Specify errors -When creating your API contract you will define resources based on the domain model supporting your service and identify interactions based on use cases. Good API design goes beyond modeling the current state of resources and it is important to plan ahead how API evolves. For this it is essential to understand and document your use cases as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. We recommend creating a simple resource diagram, like below, to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. +When creating your API contract you will define resources based on the domain model supporting your service and identify interactions based on user scenarios. Good API design goes beyond modeling the current state of resources and it is important to plan ahead how API evolves. For this it is essential to understand and document your user scenarios as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. We recommend creating a simple resource diagram, like below, to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. ![Resource model example](ModelExample.png) After resources are defined it’s time to think about the behavior of your API which can be expressed via HTTP methods and operational resources such as functions and actions. As you think about API behavior you identify a happy path and various exceptions and deviations which will be expressed as errors and represented using HTTP codes and error messages. -At every step of your design you need to consider security, privacy and compliance as an intrinsic components of your API implementation. +At every step of your design you need to consider security, privacy and compliance as intrinsic components of your API implementation. ### Naming -API resources are typically described by nouns. Resource and properties names appear in API URLs and payloads and must be descriptive and easy to understand for developers. Ease of understanding comes from familiarity and recognition therefore when thinking about naming you should consider consistency with other Graph APIs, industry standards,and names in the product user interface. Microsoft Graph naming conventions follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). +API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand for developers. Ease of understanding comes from familiarity and recognition therefore when thinking about naming you should favor consistency with other Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). Below is a short summary of the most often used conventions. @@ -138,7 +128,7 @@ Below is a short summary of the most often used conventions. A Uniform Resource Locator (URL) is how developers access the resources of your API. -Navigation path to the Microsoft Graph resources generally broken into multiple +Navigation paths to Microsoft Graph resources are generally broken into multiple segments: **{scheme}://{host}/{version}/{category}/[{pathSegment}][?{query}]** where @@ -171,16 +161,15 @@ groupings: 2. A Microsoft *product or service offerings* covering multiple use cases, i.e. /teamwork, /directory. 3. A *feature offering* covering a single use case and *shared* across multiple - Microsoft products, i.e. /search, /notifications, /subscriptions, /files. + Microsoft products, i.e. /search, /notifications, /subscriptions. -4. *Administrative configuration* functions for specific products. (Note: this - is not final and may be adjusted based on the survey results), i.e. /admin/exchange. +4. *Administrative configuration* functions for specific products. i.e. /admin/exchange. 5. Internal Microsoft requirements for publishing Privileged and Hidden APIs, routing, and load testing, i.e./loadTestEntities. Effectively top-level categories define a perimeter for the API surface thus a -new category creation requires additional rigor and governance. +new category creation requires additional rigor and governance approval. ### Query Support @@ -189,8 +178,8 @@ OData specifications and [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). |Requirements| |----------------------------------------------------------------------------------------------------| -| :ballot_box_with_check: **SHOULD** support \$select on resource to enable properties projection | -|:ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| :heavy_check_mark: +| :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | +| :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| :heavy_check_mark: | :ballot_box_with_check: **SHOULD** support pagination 4top and $count for collections | Limitations of \$query requests made to Microsoft Graph: From 16517c904561b2294b07989800e029c72fa9d815 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 3 Jan 2022 17:58:24 -0500 Subject: [PATCH 241/729] applied Gareth's comments --- graph/GuidelinesGraph.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index a802139f..6bae52b6 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -30,7 +30,7 @@ Table of Contents ## Introduction -When building a digital ecosystem API usability becomes a business priority. Success of your ecosystem depends on APIs that are easy to discover, simple to use, fit to purpose, and consistent across your products. +When building a digital ecosystem API usability becomes a business priority. Success of your ecosystem depends on APIs that are easy to discover, simple to use, fit for purpose, and consistent across your products. This document offers guidance that Microsoft Graph API developer teams MUST follow to ensure that Microsoft Graph has a consistent and easy to use API surface. A new API design should meet the @@ -50,7 +50,7 @@ Together these guidelines and a library of API patterns serve as the means by which API teams discuss and come to consensus on API review requirements. Technology and software are constantly changing and evolving, and as such, this -is intended to be a living document. [Open an +is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface. Consequently, this document will more frequently change to add guidance in areas previously uncovered, or to clarify existing guidance. It will less frequently change the directional guidance it has already provided. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. @@ -179,7 +179,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m |Requirements| |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | -| :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| :heavy_check_mark: +| :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| | :ballot_box_with_check: **SHOULD** support pagination 4top and $count for collections | Limitations of \$query requests made to Microsoft Graph: From 234301a970bc747d17f90b1da16050f090daff62 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Mon, 3 Jan 2022 18:23:22 -0500 Subject: [PATCH 242/729] Update README.md Clarified language on link. --- azure/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/README.md b/azure/README.md index 5cebe7b6..1926753b 100644 --- a/azure/README.md +++ b/azure/README.md @@ -3,7 +3,7 @@ When building software components that will be used by developers to build other Designing powerful APIs with strong defaults, consistent behavior across related projects, and ease of use for developers arises from putting yourself in the shoes of the person using your interfaces, and taking their concerns to heart. The APIs you ship can have a dramatic long term effect on the health of your software product, and that is why the **REST API Stewardship Board** is here to help! We have published a collection of best practices, REST guidance, and OpenAPI style guidelines to help you create an amazing developer experience. * [Considerations for Service Design](ConsiderationsForServiceDesign.md) -* [Guidelines](Guidelines.md) +* [REST API Guidelines](Guidelines.md) * [OpenAPI Style Guidelines](https://github.com/Azure/azure-api-style-guide/blob/main/README.md) * [Breaking Changes](https://aka.ms/azapi/breakingchanges) From e5396fabaf548930eb67d4497d1535f6ac666b67 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Tue, 4 Jan 2022 12:26:47 -0800 Subject: [PATCH 243/729] Update graph/GuidelinesGraph.md Co-authored-by: Darrel --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 6bae52b6..7cd3e6a2 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -180,7 +180,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | | :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| -| :ballot_box_with_check: **SHOULD** support pagination 4top and $count for collections | +| :ballot_box_with_check: **SHOULD** support pagination $top and $count for collections | Limitations of \$query requests made to Microsoft Graph: From 150f79d6a628e70b298d8c9dcbd992020e3d7540 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 4 Jan 2022 18:06:48 -0500 Subject: [PATCH 244/729] corrections --- graph/GuidelinesGraph.md | 74 ++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 6bae52b6..aa32723d 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -180,7 +180,15 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | | :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| -| :ballot_box_with_check: **SHOULD** support pagination 4top and $count for collections | +| :heavy_check_mark: **MUST** support server-side pagination for collections | +| :ballot_box_with_check: **SHOULD** support pagination $top, $skip and $count for collections | + +The query options part of an OData URL can be quite long, potentially exceeding +the maximum length of URLs supported by components involved in transmitting or +processing the request. One way to avoid this is to use the POST verb instead of +GET with $query segment, and pass the query options part of the URL in the request body as described +in the chapter [OData Query +Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). Limitations of \$query requests made to Microsoft Graph: @@ -188,15 +196,8 @@ Limitations of \$query requests made to Microsoft Graph: request body or completely in the request url. Graph doesn't support query options present in both places. -- The parameters in \$query should not span multiple workloads. Support for - \$query right now is limited to properties belonging to the same workload. - -The query options part of an OData URL can be quite long, potentially exceeding -the maximum length of URLs supported by components involved in transmitting or -processing the request. One way to avoid this is to use the POST verb instead of -GET, and pass the query options part of the URL in the request body as described -in the chapter [OData Query -Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). +- The parameters in \$query segment should not span multiple workloads. Support for + \$query segment right now is limited to properties belonging to the same workload. ### Resource Modeling Patterns @@ -222,10 +223,10 @@ Since objects of complex types on Graph don’t have unique identifiers, they ar ``` | Microsoft Graph rules for modeling complex resources | | |---------------------------------------|------------------------------------------------------------| -| :heavy_check_mark: **MUST** use String type for ID | -| :ballot_box_with_check: **SHOULD** use a primary key composed of a single property | +| :heavy_check_mark: **MUST** use String type for id | +| :heavy_check_mark: **MUST** use a primary key composed of a single property | | :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | -| :heavy_check_mark: **MUST** use a value property in the root object to return a collection | +| :heavy_check_mark: **MUST** use a root object with a value property to return a collection | | :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | | :warning: **SHOULD NOT** add the property id to a complex type | @@ -250,13 +251,13 @@ of properties are three most often used patterns in Microsoft Graph today: Pattern](./Modelling%20with%20Flat%20Bag%20Pattern.md) The following table shows summary of main qualities for each pattern and will -help to select an syntactically backward compatible pattern fit for your use case. +help to select a pattern fit for your use case. -| API qualities\
    Use Cases | Properties and behavior described in metadata | Suited for combinations of properties and behaviors | Simple query construction | Recommended Pattern | -|--------------------------------|-------------------------------------|-----------------------------------|---------------------------|---------------------------------| -| Use Case 1 | yes | no | no | Type hierarchy | -| Use Case 2 | ok | yes | yes |Facets | -| Use Case 3 | no | no | yes | Flat bag | +| API qualities\
    Patterns | Properties and behavior
    described in metadata | Supports combinations
    of properties and behaviors | Simple query construction | +|---------------------------------------------------|-------------------------------------|-----------------------------------|---------------------------| +| Type hierarchy | yes | no | no | +| Facets | partially | yes | yes | +|Flat bag | no | no | yes | @@ -266,22 +267,23 @@ help to select an syntactically backward compatible pattern fit for your use cas The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request / -response pattern should be one of the first implementation decisions you make. You also may utilize operational resources such as functions and actions. According to [ODATA standards]( http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2) a function represents an operation which returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action may have side effects and may return a result represented as a single entity or collection of any type. +response pattern should be one of the first implementation decisions you make. +APIs SHOULD use resource-based designs with standard HTTP methods rather than operation resources if possible. + Operation resources are either functions or actions. According to [ODATA standards]( http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2) a function represents an operation which returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action may have side effects and may return a result represented as a single entity or collection of any type. | Microsoft Graph rules for modeling behavior | |-----------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collection …/{collection}| +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections | | :heavy_check_mark: **MUST** use PATCH to edit updatable resources | | :heavy_check_mark: **MUST** use DELETE to delete deletable resources | -| :heavy_check_mark: **MUST** use GET …/{collection} and GET …/{collection}/{id} for listing and reading resources. | -| :warning: **SHOULD NOT** use PUT …/{collection}/{id} for updating resources. | -| :no_entry: **MUST NOT** use PATCH to replaces composite resources | | +| :heavy_check_mark: **MUST** use GET for listing and reading resources. | +| :warning: **SHOULD NOT** use PUT for updating resources. | | :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical action. | -| :no_entry: **MUST NOT** use unbounded actions and functions| -As Microsoft Graph supports only bound operations they must have a binding parameter matching the type of the bound resource. The binding parameter MAY be Nullable. + +Bound operations must have a binding parameter matching the type of the bound resource. In addition both actions and functions support overloading, meaning an API definition may contain multiple actions or functions with the same name. -Microsoft Graph supports the use of optional parameters. You can use the optional parameter annotation instead of creating function or action overloads when unnecessary. +Microsoft Graph supports the use of optional parameters. You can use the optional parameter annotation instead of creating function or action overloads. For a complete list of standard HTTP operations you can refer to the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). @@ -290,18 +292,17 @@ REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Gui Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should follow when returning error condition responses. You can improve API traceability -and consistency by using recommended Graph error model: +and consistency by using recommended Graph error model and the Graph Utilities library to provide a standard implementation for your service : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { "error": { "code": "BadRequest", - "message": "Unsupported functionality", + "message": "Cannot process the request because a required field is missing.", "target": "query", "innererror": { - "code": "301", - "message": "Cannot process the request because a required field is missing.", - + "code": "RequiredFieldMissing", + } } } @@ -317,7 +318,7 @@ The following examples demonstrate error modeling for common use cases: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { "error": { - "code": "badRequest", + "code": "BadRequest", "message": "Cannot process the request because it is malformed or incorrect.", "target": "Resource X (Optional)" } @@ -325,15 +326,14 @@ The following examples demonstrate error modeling for common use cases: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - **Detailed error**: An API needs to provide service-specific details of the - error via the innererror property of the error object. The code property in - innererror is optional but highly recommended. It is intended to allow + error via the innererror property of the error object. It is intended to allow services to supply a specific error code to help differentiate errors that share the same top-level error code but reported for different reasons. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { "error": { - "code": "badRequest", + "code": "BadRequest", "message": "Cannot process the request because it is malformed or incorrect.", "innererror": { "code": "requiredFieldOrParameterMissing", @@ -383,7 +383,7 @@ breaking change. - Addition of a Nullable="false" properties to existing types - Addition of a Nullable="false" parameters to existing actions and functions - Changes to top-level error codes -- Introduction of paging to existing collections +- Introduction of server-side pagination to existing collections - Changes to the default order of collection elements - Significant changes to the performance of APIs such as increased latency, rate limits or concurrency. From 4784d55ba6b24e201f7346b9214aea899bcae592 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 4 Jan 2022 18:12:00 -0500 Subject: [PATCH 245/729] updates From 13843d55519f6ed351eac00309a6cbf44d1dee63 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 4 Jan 2022 18:21:08 -0500 Subject: [PATCH 246/729] removed variants and naming conv --- graph/ResourceModel.png | Bin 196338 -> 0 bytes graph/modeling-variants.md | 75 ----------------------- graph/naming-conventions.md | 119 ------------------------------------ 3 files changed, 194 deletions(-) delete mode 100644 graph/ResourceModel.png delete mode 100644 graph/modeling-variants.md delete mode 100644 graph/naming-conventions.md diff --git a/graph/ResourceModel.png b/graph/ResourceModel.png deleted file mode 100644 index 70e4ec7c2e56b89c09d2756ede2ad0f1411c4c19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196338 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 z 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
    l@euLG@lz8po*c`t7yg>oS8_!@^DY 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
    f|vDE#rc zk7;_&10x?XZqSK`Z;!%lha zb%+kYCM&YS!dbgYxf5y<&m`C_e>2aX!?~WIiaMX>HqAFJi - **Wrong** /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | +| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | +| :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | +| :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong** addresses | +| :heavy_check_mark: **MUST** use plural nouns for collections (for listing a type or collection properties). | - **Right:** addresses
    - **Wrong** address | +| :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a "postpositive"). | - **Right:** passersby or mothersInLaw
    - **Wrong** notaryPublics or motherInLaws | +| **casing** | | +| :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces | - **Right:** automaticRepliesStatus.
    - **Wrong** kebab-case or snake_case. | +| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | +| :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | +| :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate or crosswalk
    - **Wrong** metaData, semiCircle or airPlane | +| :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw or postOffice
    - **Wrong** paperclip or fullmoon | +| **prefixes and suffixes** | | +| :heavy_check_mark: **MUST** suffix date and time properties with | - **Right:** dueDate — an Edm.Date
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Wrong** dueOn or startTime -
    - **Right:** instead both above are an Edm.DateTimeOffset | +| :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32 | +| :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong** enabledBool | +| :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl | +| :ballot_box_with_check: **SHOULD** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | +| :no_entry: **MUST NOT** use 'collection', 'response', 'request ' suffixes . |- **Right:** addresses
    - **Wrong** addressCollection | + +### Uniform Resource Locators (URLs) + +A Uniform Resource Locator (URL) is how developers access the resources of your +API. + +Navigation paths to Microsoft Graph resources are generally broken into multiple +segments: + +**{scheme}://{host}/{version}/{category}/[{pathSegment}][?{query}]** where + +- **scheme and host segments** are always + [https://graph.microsoft.com](https://graph.microsoft.com/v1.0/users); + +- **version** can be V1.0 or beta; + +- **category** segment is a logical grouping of APIs into top-level + categories; + +- **pathSegment** is the last navigation segment which can address an entity, + collection of entities, property or operation available for an entity + +- **query string** must follow the OData standard for query representations + and is covered in [Query](#query) section of OData specifications. + +While HTTP defines no constraints on how different resources are related +together, it does encourage the use of URL path segment hierarchies to convey +relationships. In Microsoft Graph lifetime relationships between resources are +supported by the OData concepts of singletons, entitySets, entities, complex +types and navigation properties. + +In Microsoft Graph a top-level API category may represent one of the following +groupings: + +1. A core *user-centric concept* of the Graph, i.e. /users, /groups or /me. + +2. A Microsoft *product or service offerings* covering multiple use cases, i.e. /teamwork, /directory. + +3. A *feature offering* covering a single use case and *shared* across multiple + Microsoft products, i.e. /search, /notifications, /subscriptions. + +4. *Administrative configuration* functions for specific products. i.e. /admin/exchange. + +5. Internal Microsoft requirements for publishing Privileged and Hidden APIs, + routing, and load testing, i.e./loadTestEntities. + +Effectively top-level categories define a perimeter for the API surface thus a +new category creation requires additional rigor and governance approval. + +### Query Support + +Microsoft Graph APIs should support basic query options in conformance with +OData specifications and [Microsoft REST API +Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). +|Requirements| +|----------------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | +| :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| +| :heavy_check_mark: **MUST** support server-side pagination for collections | +| :ballot_box_with_check: **SHOULD** support pagination $top, $skip and $count for collections | + +The query options part of an OData URL can be quite long, potentially exceeding +the maximum length of URLs supported by components involved in transmitting or +processing the request. One way to avoid this is to use the POST verb instead of +GET with $query segment, and pass the query options part of the URL in the request body as described +in the chapter [OData Query +Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). + +Limitations of \$query requests made to Microsoft Graph: + +- Microsoft Graph only supports having all the query options completely in the + request body or completely in the request url. Graph doesn't support query + options present in both places. + +- The parameters in \$query segment should not span multiple workloads. Support for + \$query segment right now is limited to properties belonging to the same workload. + + +### Resource Modeling Patterns + +You can model complex resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity type declares a key property to uniquely identify its objects and Complex Type does not. In Microsoft Graph this key property has "id" as a prescribed name. +Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources. Complex types are better suited to represent composite properties of API entities. + +```XML + + + + + + + + + + + + + + +``` +| Microsoft Graph rules for modeling complex resources | | +|---------------------------------------|------------------------------------------------------------| +| :heavy_check_mark: **MUST** use String type for id | +| :heavy_check_mark: **MUST** use a primary key composed of a single property | +| :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | +| :heavy_check_mark: **MUST** use a root object with a value property to return a collection | +| :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | +| :warning: **SHOULD NOT** add the property id to a complex type | + +There are different approaches for designing an API resource model in situations +with multiple variants of a common concept. Type Hierarchy, Facets, and Flat bag +of properties are three most often used patterns in Microsoft Graph today: + +- Type hierarchy is represented by one abstract base type with a few common + properties and one sub-type for each variant [Modelling with Subtypes + Pattern](./Modelling%20with%20Subtypes%20Pattern.md) + +- Facets are represented by a single entity type with common properties and + one facet property (of complex type) per variant. The facet properties only + have a value when the object represents that variant [Modelling with Facets + Pattern](./Modelling%20with%20Facets%20Pattern.md) + +- Flat bag of properties is represented by one entity type with all the + potential properties plus an additional property to distinguish the + variants, often called type. The type property describes the variant and + also defines properties that are required/meaningful for the variant given + by the type property. [Modelling with Flat Bag + Pattern](./Modelling%20with%20Flat%20Bag%20Pattern.md) + +The following table shows summary of main qualities for each pattern and will +help to select a pattern fit for your use case. + +| API qualities\
    Patterns | Properties and behavior
    described in metadata | Supports combinations
    of properties and behaviors | Simple query construction | +|---------------------------------------------------|-------------------------------------|-----------------------------------|---------------------------| +| Type hierarchy | yes | no | no | +| Facets | partially | yes | yes | +|Flat bag | no | no | yes | + + + + +### Behavior Modeling + +The HTTP operations dictate how your API behaves. The URL of an API, along with +its request/response bodies, establishes the overall contract that developers +have with your service. As an API provider, how you manage the overall request / +response pattern should be one of the first implementation decisions you make. +APIs SHOULD use resource-based designs with standard HTTP methods rather than operation resources if possible. + Operation resources are either functions or actions. According to [ODATA standards]( http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2) a function represents an operation which returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action may have side effects and may return a result represented as a single entity or collection of any type. + +| Microsoft Graph rules for modeling behavior | +|-----------------------------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections | +| :heavy_check_mark: **MUST** use PATCH to edit updatable resources | +| :heavy_check_mark: **MUST** use DELETE to delete deletable resources | +| :heavy_check_mark: **MUST** use GET for listing and reading resources. | +| :warning: **SHOULD NOT** use PUT for updating resources. | +| :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical action. | + + +Bound operations must have a binding parameter matching the type of the bound resource. +In addition both actions and functions support overloading, meaning an API definition may contain multiple actions or functions with the same name. +Microsoft Graph supports the use of optional parameters. You can use the optional parameter annotation instead of creating function or action overloads. + +For a complete list of standard HTTP operations you can refer to the [Microsoft +REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). + +### Error Handling + +Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should +follow when returning error condition responses. You can improve API traceability +and consistency by using recommended Graph error model and the Graph Utilities library to provide a standard implementation for your service : + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +{ +"error": { + "code": "BadRequest", + "message": "Cannot process the request because a required field is missing.", + "target": "query", + "innererror": { + "code": "RequiredFieldMissing", + + } + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The top-level error code must be aligned with HTTP response status codes according to [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). +The following examples demonstrate error modeling for common use cases: + +- **Simple error**: A workload wants to report an error with top-level details + only. Then the error object contains the top-level error code, message and + target (optional). + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +{ + "error": { + "code": "BadRequest", + "message": "Cannot process the request because it is malformed or incorrect.", + "target": "Resource X (Optional)" + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- **Detailed error**: An API needs to provide service-specific details of the + error via the innererror property of the error object. It is intended to allow + services to supply a specific error code to help differentiate errors that + share the same top-level error code but reported for different reasons. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +{ + "error": { + "code": "BadRequest", + "message": "Cannot process the request because it is malformed or incorrect.", + "innererror": { + "code": "requiredFieldOrParameterMissing", + + } + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +| Microsoft Graph enforces the following error rules | +|-----------------------------------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | +| :heavy_check_mark: **MUST** return a 403 Forbidden error when insufficient scopes are present in the auth token. | +| :heavy_check_mark: **MUST** return a 429 Too many requests error when the caller has exceeded throttling limits. | +| :ballot_box_with_check: **SHOULD** return a 404 Not found error if a 403 would result in information disclosure. | + +For a complete mapping of error codes to HTTP statuses you can refer to the +[rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). + +## API contract and non-backward compatible changes + +Microsoft Graph definition of breaking changes is based on the [Microsoft REST +API +Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change). +In general, making all but additive changes to the API contract for existing elements is +considered breaking. Adding new elements is allowed and not considered a +breaking change. + +\*\* Non-breaking changes:\*\* + + +- Addition of properties that are nullable or have a default value +- Addition of a member to an evolvable enumeration +- Removal, rename, or change to the type of an annotation +- Changes to the order of properties +- Changes to the length or format of opaque strings, such as resource IDs +- Addition or removal of an annotation OpenType="true" + +\*\* Breaking changes:\*\* + +- Changes to the URL or fundamental request/response associated with a + resource +- Removal, rename, or change to an incompatible type of a declared property +- Removal or rename of APIs or API parameters +- Addition of a required request header +- Addition of a EnumType members for non-evolvable enumerations +- Addition of a Nullable="false" properties to existing types +- Addition of a Nullable="false" parameters to existing actions and functions +- Changes to top-level error codes +- Introduction of server-side pagination to existing collections +- Changes to the default order of collection elements +- Significant changes to the performance of APIs such as increased latency, rate limits or concurrency. + + + +### Versioning and Deprecation +As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If that is not possible then you must version elements of your APIs. +Microsoft Graph allows versioning of elements including entities and properties. The versioning process goes along with deprecation and as soon as you introduce a new element update the previous version needs to follow the deprecation process. + +You must create a new version of your element for any breaking change and name it uniquely. +In some cases, the API will have evolved such that there is a new, natural unique name. In other cases, the original name may still be the most descriptive for the evolved element. In the latter case, the suffix _v2 must be added to the original name to make it unique. +The original element is then marked as deprecated using annotations. + +Microsoft Graph provides two public endpoints to support API lifecycle: +1. API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in general availability (GA) status. +2. API sets on the beta endpoint (https://graph.microsoft.com/beta) are in beta or private preview status. + +Microsoft Graph APIs in the GA version guarantee API stability and consistency for its clients. If your API requires a breaking change in GA, then you MUST create new element versions and support deprecated elements for a minimum of 36 months. +On the beta endpoint breaking changes and deprecation of APIs are allowed with consideration of dependencies and customer impact. It is best practice to test new element versions on the beta endpoint at first then promote API changes to the GA endpoint. +Detailed requirements for versioning and deprecation are described in the [Deprecation guidelines](./deprecation.md). + + +## Recommended API Patterns + +The guidelines in previous sections are intentionally brief and provide a jump +start for Graph API developers. More detailed design guidance on REST APIs is +published at the [Microsoft REST API +Guidelines](https://github.com/microsoft/api-guidelines/) and Graph specific +patterns are outlined in the table below. + +Recommended API Design patterns: + +| Pattern | Description | Reference | +|-------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| +| Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Modeling with Subtypes](./Modelling%20with%20Subtypes%20Pattern.md) | +| Facets | The ability to model parent-child relationships using Facet pattern. | [Modeling with Facets](./Modelling%20with%20Subtypes%20Pattern.md) | + | + + +## References + +- [Microsoft REST API + Guidelines](https://github.com/microsoft/api-guidelines/) + +- [OData Guidelines](http://www.odata.org/documentation/) + +- [RESTful web API + design](https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design) + +- [Microsoft Graph + Documentation](https://developer.microsoft.com/en-us/graph/docs/concepts/overview) + +- [Microsoft Graph Explorer](https://aka.ms/ge) diff --git a/graph/ModelExample.png b/graph/ModelExample.png new file mode 100644 index 0000000000000000000000000000000000000000..450f4f878887e733569273d210916cb177737cd1 GIT binary patch literal 49988 zcmb@ucRbeZ|2}>hWt4`>9+il)Lduq8XNM3fLXikr8I@#YUiMz?lo?S(S=lN#%1o$i z6*9iZtNVTTdHnwWy&sSF?UA~!>wKN(a~#L-evsk&QT`G}#1#rIEc=G!N_)?X4OZQHK*eX?qDa?;2lzg+Lgfu{$ylplLDIeGEe z)$RFDW%8-|htuYo$|FJ)sNEC0U%d32PW;{U{rmTi;)Pv;<>E~J6!@$v!=1gv&xB3Z zGWbF23~->q4=d@&3F41hGzFaa;XkfeLWQ5Aob~R+ukTQGZ^Dl%*Z<#M>@T`FJMwn4 zH9eOi!-)70EB%*OfB*bB(YseC?c>X{7;r*-jHv7u*GLxXmz{G^y zxu%57qd`9>Kb0R1+SHtU=*g*a_dxm`AxuJFKYu3kYR1ivciz=bl|ETuy=l`XcXxN^ zq1X8Z1qDS#MQy=MLP@u8-+uhqap-lh!5uC2ZMJXYgmltm@do?wf;S;ME_`^@{q%HA zoRC>ZllS~s`xDE`BiGlLm6hFQIZ)l%dAa%qU5snR>cNw?U*FyDymLHR{_69Hh}K6Z z3%@icKeKD!ZE4vbyWhAYdg`D2vump>b>W;ceyhtrzIAcS`z+w!L`H7$Y$kaT8@}`F zIvSdEX}9wY(Y$$idE0|iQd7Tucx-lGKX>R&DD!@!Fn!~w-N)#eg|!w1EMNMNs|sve z(*z9exQ=(^YQ%;dHMvGjhkvPhKXh9{;;kL~jiUFc+nw0Xuj@Z#zq0hBx5QC1(e2$m zJ#pI>^WyXG`(AjjtgK9ZD*rWD6NGiCL~y;(NtZ9^c%{4xVWCJ~{5g^Q?Af7p2ZQ(R zJ8N&xDt@jBPtn`c!>2*!x9`Xy^Gr-k2q>GLJlUXZ8iSRc{B4!>=Sj|Q9D*o zBQGx>$|Pi3c&-^Su$fcZ_2>8Qs;a8rzorzte}2dAP4!o<&5nc$+VLwXDPboSw<&ne zenu1=y7;lwXHl$qbgVtw``6^B0-FuZAG^A)3|0qT{?e3q;B4)?`vzFaVfXJQI;rj1 zsu3Nm_r=e=zJ)#LEGR24-^?ZJQRPo@b!mPae>>We+FNAbiA8(;_<<|V%@uUFAWWi#ruO7K9E(f1uGG$c{W+hhJJLTe*T#^#TU97 zX+;xk-p0+)-z5q0it7EzCqq2QaO4q{a1MbJqEDTNYiFdn51uEJtNt9dzn)&&aAf;) zw+jLRg92;RmPK|^fB$NSuzAAKTI6F6_06s#djpFJEGwSz--i`V!yhhJtEC*yCs*R3v;zQUtGN7U- zH6*}#jGwgZ8pb0K zD;`bv^C*trcS7UuUpJBkYIY4Heeu^dYu3&5f1Xn3)wOj})*mZ84K%DgkR?O!Eix1- z1OmqYocnM?oDh3Z7>cAC-Nq?Ia7EAM&-J@_MNj=XblLj`cfUl=On#EVvA($aJI+|< zvESd~zuN=XZ`oRaqESKSbJ5g6#dy`S6!eb_fp>}9sV0!qv%!W{> z{+H@>THF7utRY^+^038WX=x3*FkY0&idlrR;D7P92dOgT5w+{+dVzra?IIQ_#;e$o zTxvf0Kl`j)cJ}Jz$Ff}NTK$JcLKcfbn>eHYi;C!n#!p--_>CSVkgH5gOB@>Z`^|l6 z{x2$2T)s51hBL?RJAsJO#Ey83h*-Wf*Z8;aRH>02jLTeX@S&y8pU*a?AG{FvUm!>A z>^-(iM@PqEaT}jj`#4YH}%xhS^`&3`^{z|=UmA|VWnwn~z$wCp) zr#vg=HP8QKIqBd9jlX+}gg9(L=GRlwuo6V87>D+xW- zXTJZFZ*DA~xo4hVxFh?XdS{zu9!qCK%wD(9vwDwB4+;eQTaS1=SC^&gLbVdb)EjR3 zCQp>!SC-ghv2-}kxn#J3h2}`!Sl9h~+}~O1WLn(Al2mRw*vkL=kk_tXm$Jw?o@}I{ z^7{1TUcZHO!BN>2hhN`b%W7RVn*FG=ZBEr|S&G|MCXc-z(d{dk9(e5igA;!P>vK)e zW)2bkJ6Zy~JWB_ASC*cfU|Su}mI*(@(RFy#S$Q~OXr6oF?D-8F=slOxTMr~@c@F5% zb=YaA%QtT@$ko{K_YoEsW_apCw+ra2HQYMf>)f;y%-BA!@NjtWlC?})0(bpAHNR}h z-oYjJFMr@r*k>F(he$N9CLb>ky=F=0TK(h<*SQ2Q_Nbc*cS`7QqG?bT!td(+t&Q}_vC@^@2d(tD5XFg7-BaR+kvzjw4QzgMmB zgV)#B_rLJ2t(l>$%oTm9a+__YvA_62{D09nbE!hHMEqsQj{UQZ3Rjnk{`-9tf0v^A zpWb0IJKEa1e+`J}zc*94c7a)S*UP~5o{O`&e_wzk&ZMSeY)gs&cq@AKZ()w|Sy_rL zJVpMf^C3lGjiFA2+)VMONYNFXG9+U0$s!SpbpLA|#Ba;oC)l1WDh1O2{cUjcL^R*= zeY~O}Kq}KoPxNy`{ww@M>4 z=?@(`)Zz|Y^tat?6Ec<1VEOs;XUF<%mcy5J+Lo7H9z`knw>|0rtO}goqm`iBW3?S+ z>x;uPRItDA9Ly@t$GcHRM#lcS9^hkOVQPAM`hP2(F*$isuli|majYcEF*H}H-p6Lo zwEuQ02L}iK)2~CBb@0xdGVY%po_YN^|9|hyDI>|dacMu6JmOcd-{=ua?f)7tFdyDF zKR*g@i=uP5cX4Uy?}8&9KNjNMxPSkCVntuy1s8s5jNPC6Ut`{Ty7~rLG)px?*ikeD zFx#c1H~(zV-v#f{Am3uVnVXwi^opP8v)z*mGsAy(DrS#5<0jVrmsevLSuElNPkI#7 zP*WrJ|F=^eIqFi@KC8E$Q&j&J&{c|2AOYj^UJ?I?(z=6wQk_m^-rHZf$vCx-vlEb6UE-t)!70C zZvgKB@fStJdA^}So%)E@{#_JimA z*jAyA>ol7u`z!sX`s7TjWv)yT?2MoP7SjUOV4P=OhBHPOf^fNg|L1_$srj#C2QPwn zpo1q%I%XLk$M!r0&Ucdr(#kE)N93^Gp|jJl*h_%*{IhjnT6SmHWB}&V9)I|FC2ysTd&i(ib%a%bd?(Q<; zS8hE;_D7$*NYzHdpE5U>9@CAf?I7c$ADpha*^HHT7x2EfcLPHLB(?&TJ#yqoLG7#M zKRfmF#*G^vQDx9I5OLZ5YQd5ETXT6ejOiHnMAH7O#P5Sf!;dvh^c0u7j*FdtcW>PWrtcri<~njUMJy^J@6x>i>b4PP zVgj$vs+_NAX=#zrEwa-~q*c61Wckj<3aAZV-#^5o&;9tpso?7a_{w(hykVAdF!Y1o zfBqRe^M37|lT$hxphoPzV*Ac>^^veVar`MJ65b4WY;Qk;b>lcFQBcLRRe{%$zf zRGBNnrbYFgorNItKfb=-kHU%Kh$m=hAN7M?poikH&p7WR-WGyQOTq!3YiqK%m4QUH zkqjcXfZ0@(l-93*O`(nrNizp+=GLx3>HCuZzb8?krlGNX;pK+VgGxi>`c&TwJi9pp zVI!*;5n?sXYv39cAfWM(MNSr;1A88}OyiRGF)-2FgdMGW(75DtuQ=)6``kO(l!lYWvzR&W$Q~&EE@a>qJ#! zGs{w@v-ewTS-aqVL3E|KK`T*+cqj<0}Y0_>FW@`dDq+EzT&TO@MNK^Ce z=Y|+&e%%bC$EL_&HdF{CI%>|Bsj0R3aRX1j7{x&?i^9NTXb8DW8%%dd9O=qCmAkUy z4MF7|4rQx7)jc{Smwz0Z!XV1BLhdtk6KEryeSH@3`qg_7zIfpK_yjbkj~_ptYe^}? zUUc5oQSg{nOl!zFReI@2+3%BRLy}H?f;_%;jg29Ec*yXgN?4Bx zDZi0N+4++4TEYpBi+&ADx7zmHX^$UHH0n>mc)}r)`{<^_b2;p|AKkR2NZsX;6uE ztMpf%v{gWQ{SE=6M=U#>dAIBCLX-AagFMrPmWmaeN8v_&`Z+y420)F|fHIfDcbx6Q zg$ph8rw7Nz63{juoOp=SaYwaS?qc1_Cr(weJR9O!DB}0n3go*-bl2yV^7eyKg^c}J$@XRR>+L(j`XYXjlKCpUq4Fbzo zbU`SU=bDp>orhH#1JO=V&XQ%hl{b!9nZ79K5EA6mO4!L$oq@fdk3^4B`)%16tLyu_ z)6}2;25QALJK9ni7$zzYld$<=Ko2fCFA3CB>xO8Ee__lbT$T5O)*Vj~FyQmrDVK6? zXnb53Wq*)Ng78fKeEJv2UdtkSQ;y@uv$v}F;f4e~DbTgG8b<+R7%|!d+m5h|si3f)F(V4(vQp9XR($o6`sAwpu zsQN2Bd34t6SwlDK&|zYFC!^0$2rbQ5aGkQixU;+a9X<5eSV@0rWR!m#hqNm{4!PqN zi-y~B$%-wY0F+As^HNdXN}v>wpe6tfoaC}oC!kN`cSW1!l@-m{eQ#0|b{)Nm#?x@C zGXHeV`m`D+k?+r}8yLAlBdm^`Kiz@@k;QK1Kc`x=ukn z0mu`!s8|4oN@SC;L5KZVmV>uM;fmSn7n~*A98mqDXzd$-;qrFPc;Q$^e|oH1@f~W< z`bv@2iP1|ED^SLBGlRrfTO-%!%v(bEKv|8EaAn?2BoRU>mw|5((ho||fY7TCAKX!) z#de#c$g9p_`{IiH^pNyP16`J;BxNFmZ&{Pm`H!_%p|*`VPCb(f(^cltsLl@d^jg|) zFeabXsdF~$T+3Vzg^ic5V6L>bhwSB*$h%D`dv{huT4^jVFO&NPy3n`y`PEcz7~kB; zYG-uu;>C{Db^XOn;IloXef|ihsIcm7G-97*9x3yO8^q|99vHsB=m8Ae}8ZtbQW8rG5~~Gi9_b{Yl_eU<~IVHObkyiD{A{nWO#ogH`S$2 zIG1DxwNW=(FWIqi9jlQz%9XKO?ncb+%#(Uiwn|iuKE5(X+lsdGs9d@m16@_)pcnvM z;1_#a+r1OIK#pX-{T3CaMHE>C$yjzmfn*l3fCe}RECLC<|DIkppn{aN^w&O}wX;Ba z<;lci`3u>vhV!*>rdqr22OJM7jTfRaopLZ9r(1hS$Kq4GV!u#cDytxW+{ zVp7wOT0<5v@cK1*3Rr1*ac=*!f#vIn+jDQ@9s?o)kPe2u5&z7uvco1pcV~9kF)EHi zkAyQ!st0QCe^V{^4RR=9)L^@8V$(9u&t)W^l1*`bUuP%j1bMuS)>Oi_g@c`)eNxrd zb`*H$?XCKw>4D2oG3Ql91uU3|v*@Whvr1u6$^;URZMlJA^xsQ^ksOXOm zEP6Fp*mg3KtTg)ds|MoV?$v50EW-8v%O%d3qXzPZ&%KiTCKyqlM=2 z%^aMZz|S8M6^LmNl+?b%06{Dq9D;?CdGpd8ZJAa<4m2UCo{cvzEcEB1A|<5X`Y9q1 zco85$*4gCRPX9~@+;+9-cdh9PmFOQ%PEH|bZbNi8EwT&QF0gQXq*Xsx;{@<95CbtH z0Tq>T|4vIw3lMR=$&}Mq!)5sZm!I+GCJ2wC02H+CeZs z0Y~(P0m6t$$mYTI8(G@GxWI>iur+?I{!jqM5=C1=7=7<4{sstwA!9hV!s@^)s>I^j zhS1k?uLgeqs*B)0Mh669X5^uFd=mN z0T#geRKi#Whlcb)(EzRMLYX7_*RSrx<`T^aqek%KQ>RXW)Rnn?`+!a`m`c_38l($~ zRAV_+Y6EDtnFfGC6GfX_iPY5nSupVTE#scI0p!^#!oCJMn>KPJYq`0 zENG(ZX|^N|Ucb5}5AS-`?TXQ1z&?O=>kp)J_tB^df=o=p*biwxs4c*;`+lu_{xTva zW}93NIv93sH+Jpx4i1jxR)sYOL<^1ub5=)0J$NVPfp(6L%}!K)RyH=V=$1IDB8P4l zR7m7RUx_1oGXr-u#qww1VxM-aXc0^K^NqATY{0W}-e~SQoRR?O*{T@*X(Jw}yEhHk zK6C0P)QSR|x3%G%z4#lTq|X?_0h|7^m{Y*KfeGjr35JA7k+AOozQQo?v{?TQD$$~u z;kvgktxq43i88z$?#H4BwqPIB40z$$fQ;bh^}mPKbnYwo(dz0d^oZG+8P>y>tkC^6 z$^213bP!49rPnC>EgvG3a5>- zVSS2}xKJ&KSuUp}G*}GbvS>OyQ^}+($^y=W0-y?lj8_FvX~qe%f>a_(0p*f-RKuT| z>)cSXz)plPi}WEHk-s=Ar+=OfB8f^l4=d)Fz4Y-!(s^`UXv=s76$&3myJ^!G-kG_% z82yCz_w=rFnLxF+r&3;LEhD;){EXjC?esB7;NO>ATpnP*5cYLpti#{F zc>_2BFG;V9FQf9`(1f_8Ts~W#(z&sz7_4vqi3jAu!Z|$qqStOTDd@1pO`N2#NoY(c17A{$04MjA zosN%)*es-jA#m=>A@IOz5n|A;PbmuBi}o%e9;$*SkNyjf8Kt!rQy;y4be7qzbFIKr zWMF91;`8`7j?hgZCm!~iA473P{ARF$T}G{ZsLsM%O8-OvEqEH01NB8y1F_$=blVH+JQg@Nr*)S0(4yJ+7|pk z<|EFUj-DPnCq2~yfgxktG1kmY82#Q^mwi3DXiYy}_|(`S_99Z!zkOG4ViMMHb)_rb z(r5L}-TuXe4S5Q_%cbDL+s9q&K67_#f63x$&8*p=xZ$YhH$Xb_3n2A_9@AE6aEP_s zJvjC~&u)koYhf zhN7uEF!$>h)b)9!!Depx3akjqReQC=$5Ooy%p?}()D~W~NdI3mkmu#DE*YxaQ-O45 z-wm9Q)bo@zF1Mpav)=J@Qju<|+S;Ia z#eHh6K7$S_rgWSv($t=qLtgE`PI+wlGoSdg{6^G30;OWt)-$kehk2sI1oKVLAeva( z{wui@J2?<84a2=4Tuvv62&ti{e>iC)>gD55{+eCblg;#={+BbcYyd9kx6AI9rK znOh<19l1sw?D~CT!k+FvfS#(Gsa29cB~xZF-Qz>G$Ca`@_~y3&p#*$6`XtJNR>&56 z2-znFAqi~*{EHWRsI9FHR3fCCp$HoYN{05;BQ)KFum}VOE<-L%marfC{#`3nHT1l_ zy?`7I>E`G0@rF8@4v4(>A4ETVxL?Q`L_e2P7}Y&Dv27R1Zf&3gvlJ)~0PTI5OMxLu zn*$jexwjuiVO5dJ2LHl--{DCuFPGu`T~Sft=%?*qXirdeV?FN6)Dd@kI4O!PsqGuJ#l)C0)Kd>l{8bB{g;YyCaGT z1qDUVi$n@u?GZ}D$}Eq4o%R@@`)^&IFQhxpv3}7-6Xlld?x7c%ySO}Y>Md#ax`I>6 zLZ_Y^UKwBn*geqDZJ$VEi3TJs)xvm)?toE7%voCqnmF7Swp+C?`Tzhhm zg-2zhJ(c2XLF~@r@vHFX0C)=XNI)T%En1Fp+y4)!W5>tny*k2f!rLt|>~B!521r8?YOC0_0lqB;!q}mrv9&fR z31%KO_A36!iRvdPv_3nhfBcALv{ObAx<8E9@H&zfyt7qq51rk~Dr)6#cu;fESZj~P zwwF+PB#w$N=W{9xS(i^=c6J`5dIixfhnnrk{c!1U#B&}5GVtey< zg+SKxF1vsI?nIIKTs*(J;*FEzDmb-(Nk`O`9@HCR{a_H%EgwD@Vg2WT%0KJ8RELWR zWpo#hL>^e%d`QwEC*g^D#a#);WOa*9F2fDo!pr1`c~8vC^ffZJLeR?y*Gv%2ETY)C zQQmj?ABctWlkM6PXKOdIo@PY7QF?L#PuLN@2M8>?8=A|h9@5Pq93zVTd(I)jKkk(_ z+R0MXZB2I4YCQH15?mGn&0Gua=W{)8$Gh3GvFJ(h<*sfV>{D zZb2xp1Ou@|nN3}(l(gAEcGDI|?Mxo6 zodpOG}C%RD-mI-p_9hEpA3Q25nhw%|w$ILyw~(qJX^};j(aYjTYOU zS*u=u@F+qFd|;M~{Uq~q>R}Izp{@`-Cx@C;37K31=&aAOr^N9>wZcz~W&~c;qWEdb z=*Oa5pR=g+y^4om!9y^xh$>!rU6^plDUGbSf$Z%8oO?=xG;N8YOeXF`{EiEXC>u+K=;lO)7oc0EtA(z_p(r63kWGTv-1|4bsk$IUI zV0%Tf;S-A$s90jv%2$^cqAb5-_@A@=K7#(u9F)=wGHD|n&UQh*jk-UpEv~9cak3P3 zTaAw7iV*C{)nx8FqLyR1ML87;c*B#f{(ei1sN01Fm>CyQ{CVlePoEYqiC$fD6$oHz z8%&k~bb@ijf2tqbn=0q+-X}xPBlXS^5EBf zZ!GFf(*1;tF2D-*A+r#?x;V;>EMJeH^7Qna372;HtcJ7LNxm+X&0>s*>stqgdWmkY zw14LVwb*^JMGRlizc9L+H(yxfWMRV#7Z(=BHbpS~@v#Z9NII}wNqP3{89u%S0Euyr zT2-$f9)yaD%H{^^aIX`~sPWvg&NR56278q#PA@_vSQ%`U*gnF!R+qT!NG1_u4vaw8 zIW1aa8^x->+aSY@ZasYXu!tde&3?;XZ4M*@v2Fp&hNh$E`bG411PKYJUwCx8niKz1Hb!25_t*OU2g>&oVzuf%c?T)6iy&nKDwe~J4AMM z094$TY4N#upZbeEh%e%1=xwAv0b#JKLr{^ zKgh3ZomMDjVQO?m8Nfv!ol6-h5*|Rdxbp48?Pr(8E&*Cnti9nr4+-0(=O&@pP7)+S zjc#YbF1*95>olIs&`|QQBN)Tia9%-f3s59PLhV5IL>-z%e;F#L@zZ@|a==uxw!-)| zJVj*}{*+b!dZIMo-9Dcx!Vcj{!Q{9b;W| zQA5|dyVtQRFSUqbH#-p#zkh!VPiKO%AH!%D5IwQ+DytXiY|qovN0Ce(z3wh9zzNU7 z{B_M=c*Pobq1)zIZ>L(nF=H=S!sGwFi9lT-k>TpGi1OCA_gQ4zABFv9pI7WG-F60J zMNhe%y#e+fYpX9-BThTNX%;mtbK!lG5Bi|i!0$f!v7yy_yn|POLU|vK*I-famA=1x|;zPJu4w^jmKqD80u`b6+hY_U5M6 z^PnIdC6SMB&2cv;wmFpDQ}SW;nq6MJEI?t`2Kj{WbB|e~szh(yB6;xqyNqE)Xa>2C z%06@XAROxK|9%=|VmG>7^xD5y8=VR{ZADhB>h7sEXtAxq1U611qq6z4L*2WS3G#kl zf@ErttbEatj@1Q#aH#d(TKIAn1Fl#}#T+HHAaJKK#%sqwRdRDQDp%44Q%5@aS5Ak! zZtt0|Nxya{Giz3%n|t%`Ujedm>*RTRolg~$-yYSKj%Oq#Bm&gTI$HrV>5J_i`EnK` zm>5RI&qqo^BayD{E%r`MB914psF^j3SpM4MD{6H$VG7fot?qE63Gi=sIo06zAa_~& zCuosAB}u#xa)bOP>UHbX8%h^8RU$y5B>%kKmY0&z@U9n~2rlGQl~}!`y{Hv_vq2}l zcZR?ASdyYbZxmKIHF|mb_oCnjosTo$Ak%WRP@rAt<2Kf4O&uRNtG)-DI3Q#gQa>8BsDzQ{H|@(9W&lx zn_9o4iu>&@zgLL8c5B#fN1*XuOg-`^58vQ+JM#HxN$8dljZBb}=s)k3dw-AUieIHm zpqiPgXT~i1hP<&lnLIn*UIIWddm|lCZRkfhf`9S}1Tst*HKyIu>Gt!JiEG;?kluM+ z9?oY|CzR>9lJ-yKS0a9IqS<}R6>F|mrBU*}PgeY~kY!{IAy}XC7E7ZJ{C+P2%6}AQtRCGiksm!stH8W6BH-B~L1BB?=As0J!UpD8nrkkHrU4Gs1olDE4~jc7 zV%$zCq=Xm&i>LaoeJ578AnLYhg$C+c_)o->oq!IJ}>0ru^hSU+h8z6bamyVV; zl2OsRDvxUyriMdubC@;b(+nre-F7`&cIaI3Gk$Lng#$X+Bsa zXJmMohu6Q}-+_4-B%Jr0hC-XTWchh@UqB#qd2(z}=X2F-UEeIBkV59QUopD>4CUme zkB*51DXawB`!gnsDcUJ!%BFO(E5vcvi+}d7M|_KlY(9Pz(+dS=JwEZvunlCNfU>%K z;$F?#@_h`=Q6H(mr!a_%bl1H0`|^c)0N~QD6vF1cclM?yBs3G!*BfG~bHt~>c0 zZ&>c;Sr&3;=4;4j=ga0#9$8b^Hlinc*~W`4Bcx!Dxx{5+xOXD+rp{YgIXQcEwCtUZ zc%&6sB?xT~WK2z=7Xu%8H8u|soCqr8(ZsNVcnMcMtzerdB?U{3hKf@!eG-jtvAxvH zUVbnPaSSmk3hrkA;SuXp9P}vkRf>My*F8O^5JEq9MRfB2v$9misf_z0mKLMH$npAQ zjg-R&gu;S?g7AbJyM>k5BpBZ;ZJb z622bUU4H`=6^i)A$Dd)u z(vW%3oG7k(MWO(jBi{HmY>)ssCp^++-M*61KLNk%IGKx$h1Xpc2TJw!{%st&2 z2K;AZXxZM@EEuI^c6WETdtnw5Qk2~T!N4uq|IHr<{gwmBtSn_}KRK z=oMJ2n?8jAgZ4)Wa5V*G`1#R87o9TXAP|kbEcgDEhw4-VQ}+T~LlIn8!5GCT4R?x) zMwWkp60NU)8$PS{YnUNw{O>IQK}^N0TsctsC%`*at26HJ)}0#tI;qOSd(1Y$yj`y=HADj%Wyyoa7| zO?^)tT;u+ALvP6z=DwQPhr5QbcAc_1%wc15so7rDwZYMV^;E!i@UFl{Q+|6SZuC6- zESx+25R$0@_d%yV^PYVM5avuPun^*(63`Mg^7-|$W1#w=@aX6pzm{S;gYm67w&=|$ z-5%VyBJ`2185*$5zB52BM3*#bVN%8v5v~n!1F>C7piAiaZb7c0PzHf62k;OdoskS= zM#%m!g%EV(=x!dBu*j_$M)=I(7X69VxLEIV4!tGEHAsVmlmgx~y7Cl$K*W*9(!9VG zb6~Mt>ghxL4KsYo{DWwZdMf@Q0tz`EkjlyLf}Xg3dgf$lPjv$t(1NG8_gvH&%=qx? zQnznRSIh$_*P&`jf_h<$sNkdkJwE|=8p25Lv#XR}+?oCD4id;rC`l>M5 z6yf!+B$Ncq^DDM3Ei7Oc6c+<7JzFvM{3?%Gq5Z=S z6m$=9GjxA4X_ZhC7&s0`Ua2N%B3+RcwDFJ5a44Yj@MB!t^Wen(J}ZKG+ECktR8}hd z8bJ8v<`^9d*`7IKxHuY`;X)`14~<3SO<<@}262N2U@-hb=bhVRlCLd14um!AaV}B= zDs|k}jDsK&+1g=Ta!bg2yc3X8=CLAm*cwIFH>CogQGWxetM-mKAyQhEZp$1X{_W9RtmN&^J99G zMvu*HykHzDDgqQB&>dq{KgfBdq+`$Y%ozf%Qod)Gu-ozrmF-gw^|{vm{z|A-6{lb0 z4kR#@7^N1aYA%MNnr1)925r#xH0cJMkTcu`sbAp9NGT6Xk@eJnVAY3PghIVH0*}EE z3ni47r^*mo_%-QggzWg?8t_i1*zlLLTjcQ^sF8M>1XF*VTE5ZJn!*JO#K>owFG}=u zHEQu&T2UcF>O^V-8xRfNe&wD(Cfn)5*BkWgY76%+mxugeXv@v94*!K{55 zx22>R<^Fa2LCr>&OsqdoXsy^1lbD5BFI3^(O)J1&JH zfv>M;_=>WRo&ha)6xVKHmjGQk0R;!G2B76F$_|8WafA%c08|{4&7R&~T&^UPhdbI~ zeuPCHBcb_t6<$nR@b8HGmr*=4m=PG@ZSdL9o}R%pjQ>tz!U~o~vyo+Ba#B0=fPKdv zZtisSf_BC{_!Y4Oz!-#8TS$t*>0@bBoF6O&7!uzZ?m`^}wbeEdh8%}WlJoa4TI#GB zKXyD#HqoLBlPJ41Naq~DyUEGDk)V8=I5nu|&F8)DtNCGM4T97A!uyITqbZlEq^aSE z7}Jv7AYtUZef^}g_fNroQ@GWlspG2;IJ!;`zI^F9cZXaw0KvH8A6G;2e+OVmA97fu zgs{=UD=uINo_7ni*!YP_{_fESFfd~RECW2eJ(`$_2%Z$f4M^NwXgMLfUzEdiv@~ZO zlpGaF+D~9jBfLor#Z6dT*v@&BD z-O!uBp)TNw;LL=o*$WFC8sGdx?-lsa(R_Z*&!-%A&U*Ij7Zi!HEo zsVg{}#WBP;gmV=Gn5RQ6Hpw-=GEpi@%%lYR3GpciN?$N(^PDCyQEsUNd28M@^ z#v)bP`w?d%=B1aA;6;1z0n(W30?Ur@QuwTCdj~m`Gcgq%9O`xZay|UvyK(gP=n{7h zS&ztPCd>D@SDAqDbsc7BXO{%b;J6D=dY>lejlS)Q$rCC9^!6y1_r!P#x0|saxbL7} zgfbS>bc_=hKyW{dRi6A5w~^a6M78%<8q{ZRiIeqoVSO9}t1kU%JXUlt$&gP}^eI%H ziZVX6NX7kE`25wN5-?0IVNW&G4eUa51G~W zNjj))(6Rl5&}FxlnRA20J5pj#I9cE!h-yFm6se8uUz@-D4kxFREXlyk8gTE}f$(Y$ zrHsnb_3~`<@& zzt^XvrNTOuYy(g@2d}SFPZY}y3R1Pc`eYeZ5UznQmbn7_(|X^{3eVlvqP9}QOb`!~ z>)^x0M4+*)R&fgov&8haWWAJ>RJ$f|DY%HDn$+DQ2qI~ZQ^gFv?M45J?o+EHHCOL1 zqZ%Wqbn_0q0v{U~8iMJXE0K0*!Q)fqk}!@|yoePrsD+}g;(rpyyxnOd_8}^IJ*HCe z7D|YOT!t;{>)rV#QDu;75M)Qsa=`N30~y_>C;-Fm%MhkOEFwogWL|%lapXEG(B5qn z$|iurF6k^ogM+(xs=|%~LrUI6@zpd-#@&xsW(S2;NvDGxYzuJua;{^#(s?t^0TNDq zvJdiNK2J?yEHHpl)tEJ=`vnemO5f+STZ=HAfJ!#4f-%NR%+YalPRsp^(5KYRlE*6E z;3BTYt@!wO;3{TonQilc#7O>cYd*!eLx6D$xn1u(@D++d#~MeJ;X@-wOauu~8%(Np zfEA~J^`r@kitz(de7ZHJ{W-MIRt+%qSRQ>#Dd@<*z0=m8zq*J+*nu#7Z%}@*9Z7v)82fd@Nbw z0^0bz(wW+@;B8j4Jk~Ou`9Ns|!5R!Ja_V12VHeYWZ1K`Z3ib^=WrNZ;>!yTuOkQ{H zROF*v2watWZD-&I_Ae8X~If!s94i?qrp*ncdY zfic56MWyXk=&U*N`}4sujP@_$hB18nk-?;wew9GeYoQ^yCuWC&BSxv%3KDL=fr!(h zgW(akiC!&P#l5T7z86n|Yb06T$-grpuo42Eb59Y_0_xry;Fh0vBO>)bNyh9dBnFs)_^=^Tbu!Zta>o+NtMlZSQ&~7pleZ zHADTkzVQ>6{xLZZ1gjo0t^*+*93Kzvf1h+0DE1j@^s^h#=Ail>S0OEjU%yV}xDS*2 z*pgbuzKX;{tJo39tNPOq;G;`IRo5rZhT)p$L- z9w*rSXExJ~YhiED-Tj`oujBjy3zc{^N#Ese(w{7oE(ck+;$S~01%Q-$$p zeclbqwQU8d=Qe^y^nX^vG2re8FHaNWi*LB7qPLdEjz-0av2LPIi2WuAGr1RL<`hXU z_Ge-;sIEcE#47wTvYqHI4EpsI(dS4Ez!P3XMn)1AGnA>kH1OA#xVm?T#6Z7sBQe!; znnvni)=?ZrTF}Hb6tUd$@3zfJvF;&&vf$d=>)m^v!m!wri<+(`1@3;w4nfvfmk-lG zW{xlqsX9<{PfXdHOTcbpBi1b`Cwu;^&$5br;Osn`NJqxrxy3Y-;0m23m1A6x>|__Pv-F zAk!AFK7=0#uZi1g^_5@2*?f4O$+RO&Pa-jT8Z!Q?nx*sxy}2$4m53qF4u3JZ6Ltx! zF4ft3+bMpC+=Q(=M53y*zVzN4tVJ-~nkHad)#(PVL{5Z-g!AxS7CKIqdHue?RfYA{ zN^AS}66|+h0*&+@mz~25{yf`N_mAji>=9zSvs>rrOnAuBN!_(w^hqWSmu{qouFjHW zZIk@PQ^XD>`KJfM-kog@?rC@W-4=->&3`^%E3NR_HqNqzotyZGFGwKr4dKKE1ALZ^Nuhe1Lvxj{6*sIWQDOIH{-QlZW`J&eHSJ}MA0+#ucq;8MYxkS-; zkbU*kZ!asVxE-9HnHd~;B~m-EnfRR6t+_LnKDTx?W;QysKhSMS`!SQByzK5^x3H^2 zRMb~^x8p)K%mnpI)hD!@SpMjr3~hr^t-fW|9&B7+>W>``m>DE*PB6@*>DYB`^()uR z+P}{3ZEOn%pO3fRSWjMDcE72od6Ev8DweTIWSY;~u)=H~RiobZ@SX2+cAv2Gk1%&- z$vRyd#3g=21d8Sp3i;eS^bDPr7RC}90%8{S^dq!tzHGSzC%5_z?mo15(j#F# zxxu$uQyepZ(si-?*^*Zaj_RKDO3iIw9K7ybfm!x}nT~?6)~$yr3F3VWp2NmJ z?Ru0v;`Q9E&v#fR`G+iR>+~=9)=tW#2%NpdmcjQ&OMTbB%`+iB=uRem^X>Fd{+MG@ zguI?WNFVG2%(LO^YG2W?v1L?-ZDwk057~T3f|dKKp$q>3G3m69x1&6Qg9nfLNwLq& z#79&#{*yl4lRfTvM*h*-jk}qZ^RBYI4Ab+xs)6%HQHP>2hPP3#nP<7SwS?)0Di zzHEMjIe9uRLxS$$;z_MO$i+jow;!5uGu>u8=r#K}7zh8FMvm*t%C%oSTzo~ATXI`! zB#gSv=omWrdyY0lu#RWuH|kxxa(X17QH!iP(-b?t_%vIPjyc;Lih})VAZpmPE$`W! zJ-E%4Ba`YW1%@q2`gESliiJx1gDmAAZl_y*faBS6+qz`lq)6cVZyh)6>v`;@t`Bz)|40XqX z$b-&}_KLwa3SY9~pKX_*xWdqU{3PSW4$CwZ)-M*r4=`}tapw~I~ z06x6x{Xrm}D0iBZuNw6{cc?2*YbY!(&Sjn)9~W@+Ut3z~>C(X4CS)wmBce~IhueoMcS<;ea#+q5w6+qydK2K#HCvo4!(t!f_Ac6!(K z@zcAzvIi6dBJ|Uerc?YXxD0&Eq#;+sM7I!ot~Ko{m@{_fjBk&P$C|$Tp-$XtlkK|; zLcs@dr{2Bxy0_*%`nrPjmfyF7`{L%)QyB-HVsh`G<_Gr&(7t$1+#90xr!~-yB%d}j z>xC*#xbXLio(3Wq?V9{oB&v5;!FPxZ5%-~}n$l)j(~6EyK2!p(AP{S1Ep%(4wUPc2 zQq;9)2lO+Q)^VAXUdjv;w@B?%syCMi+LzD-KsA9GRH1 zEdYCqrM5h=+_5x%R#pfz?<)YDi*C3mxRJS^@+{n>2OK^4<_52r;LDW4EM}oTzvlD?iGY}%kz(tDSing0eA-vokphVCjehK)8EKOvD7}}K&=6WhUVtu7GZ&J z0OWJ%tpr`&m3z}puH$^k=s_r~|Gp`)0oaGJzE8gVrbYaD3sx=AS;WvpLI3O5&pd&! z4=)=G{W^C#4@)NsoO&)%Sp<6~q5op5--1YZZh+Ero!sP~p3YtQ6{3q8ogz)Qa0g6e z2lcX)EAF&dts3 zxH6R%7bg;~gR6~Be9UPix1lH034;@JZ)5kXid2C7cdTzCMj(J}b9ry6_4PXEM(A+&jY*=Qm4y%4!j`SE36x3zM_K0SAf zS$8nmOg5lV3fI9us2x%5Iakx)pZ@DLrb6&ebqjOYS)PaWWlDPwBq4$iJHu@Vk`GFV zEAB>;f}9vc!IT1#Qio404$!Ev&5u*oqYc+g;~&Fk0t0@`R+8RcOhcZj4^Ii4M_{e@ znjl8YlB9x9I!fM}XCVO$^{0rds;Zh7Z`%C{8XII|od)$1@l+5vw8?qMIAT!&FpVcM z9c5d?rG^|qhCVgL<4^=W3k`vVH)cY1p6@};hfmU4<|-Wcg@uK`e+b=%jVl+v1|Q&_ zO`E`ih?}lq>qSXDJ82l$UK>y7llPrCkK|N1MJMHgj}A|}HI$0WwUu5!9?bIe!(RQ^ zveH6@bWah(b-TCl+r$?&H$iam?B7U?cN65SWoEEG+#=|lY=_t`Fb)an=Dv#2$QyLq z!r2af-TW(>@|G)xBF@gv!T6#Fhd?GKD9&ISpNi~bw6=PZIRkK8R}Z!#DU7Df?fWVg zLE}9%NU{7D6^9*DaEmdvBykZ&4fs9n83xwWG89XG2lTa`@5+1ZJy)PB|6B%Rz|{#} z2S-OaS=mdA!ngKeE&>C|xK8DB5TM%+A6_uI7{1!OImhpqr}nwW*|Mp{#Xs;Z3)Z_y z2L~@l;I5rU@A9-wEFjK4M^W#3cZIS3_H=N_m!ORuPoJvA2`1jXI|}QG&3S)*H|#I; z8G-b?eB486N|pJAd=zW-8lptQIY5USRU z$jJ8*Y_k4RYPzhcpb#WBdR>($}jWl-hbI%6;ys)b}ePYI1;pDT6san(-V%YE8<3-4z z4oPE(IXOSt^3ST;U-uOM=Ai22lKCRB-8&QiZmTPl$;mX~Q_|hH+%TSQe0Eme9s-F6!6P05SrWLEmAR+;*m6thu&az2j#csBpU zse1KNT8-~jT17Gpqnu9ZFyQHn852Ih6EmYzr~DH|`yEtAJuCL!_;NQilcXF=T!O&d zzpobZ$I7xLOllc(+h*}?01qm`fWWM0NvSlOxp3h~7`a<;WUWi*9Jyx|F(?P$E~kN2Zjfs6feW~VLU<_LI;Ba zOSp-V9I&wwSHqBW&^rJGaEpHlax^Z9DuEk@qBoUZ8YC3ycH#@rK>zP6{UENXVC(=v z3)cc$VSf9cc^}ubQ9n?OaBSm#La7^JoWG9j{9#Nm6~C0lRDeAdlxE#=j-+;Cq$M9>Ztj z&b-}~jFl|#T-5d9i4r_-QJMaBO>C$kwc7VV;etImviZP9jOHIder&IKXF>{2 zSa{L1E5#<6iz3>XG(+W}Vn8k;ymbGMsrQb@x_{rtB@vN`$Sh@CN(s?TS=q9c2rXo! z2pNg2k{KbCO=xK-ltPk`WR;9a%FNzbzvJosc|5+q_doCZao?`%dcB_G9LIT_$9XxN zgvE8L3q5d5zo*rc5h=a%T}h56y>WnMG_EM=>(|3~p2WeV&iv<1pw?U|_qX>&9I zXzy*L!8&hbWQ1$)#h&(eOKlFh%VtlRtJ&O-ZaOVIV2(p3(|udi+42ROzU^_G{Fw5s zXUFAjv|lbO{E($*gZ-LPlLr{*H547YZt!rCxoqtsQM{8liPkC+2g6^uv7}iMI7Gkq zP2D)sTG6LXYLTZI?z_^)f*QxAy1D+kj%JkZJ&=6Wn6r(9+S)QyLxm>yQ!%`pVte)K zRm@F0J4vgm)WuL!S=J*gN!tuKa1J!vdI8mV7Ju%c?m6{+Eop4>FbYt}kkRmdtCvLR zB1u-RD9M3hqIqkZ5@-aFXdsiI|Ikqg0;xiM*$(o-r!1P@ny0N#a5Rc@nE-|Z_{lFL+AY9OVHNc}3i}Jw(1|hwPM~qxKU{>l6?v&Yux-%Rc#flr{kwqGKnVq+Ves4u zkf*_+C;F^taPSgfW{yOGiIG!0wP2%|B5Vob8ud?l6@Id)r(JS$%j(&nqZz6WO^W{g z8@Z*!_~fA;04*CPe))U%!J0Y;%m!zhXxVwS_r{j@C*8I5e{?{fDdbqQ9Nb%etTg^J z?f}-r1xf*q&k?y`IqdLLcnI5`%*RR6%x&VfS5h6Xd3mFQBLC$T5Pz>+X^PXk`wbZs z8gDhZ#fCTHMS)TLge^f`UhhONaP$89Mch;@1;k$V?cAvnx2>3nyqP>a2ka|jy~a5f zbTdBDPoo|-#fi^uQO*m#e7NnC?Tg(og)k(D6LhyrAaq7QX=c1eo`nsIC7U6_DQy=Q zSJ&T>R4+hxh3z=#IYA^{x7;k*ZGbw4*|kf5Y8rlxJ72?vQN=lpLBSkmF+UI&<}>yH z24&!JlCs#klQ^l=12=zDrt1e|_fy{wy1ZrNjCqE7*@{R;Pjr5n`Bv%nhAZ5oSEe=! z3)Z`!Y+MrxzC>{xWP_1S?CnwH6_L_!(SH~9>VExyjA@hi)-N;uf|G@n^c?@S?(JPH z$+fyWA~zeW4Nsl=iJDmdhnu=7{igo@{33 zZ4I(S4&roo;-jJDSy5gd@J|Ny(#GqfU9-dOm3NmANuZ`({j~K0O6J8$s|55hIhe~n zc2!Mft<;S*5NumvVN+2-*`u%ruQK;j0=LNnd;K^ot*afvj#4 zieO8dUYXRKElmpbZ$A*3t@B-&A|-V`XFeu=M6^ z~c2nVDX<{oZp< z6h8)d#*l?StHdr)+GUj*o0FDcYLt_wwCBHDs`v)55v0S^Nc^t*i5o~$_q zQjBV0XVRcIPDR$yv3IU0>lNfkg5@qCyThWq=U|_mn2469>fd+%)rpt(?EIE+`nv_! z2ccUnZx?te`2C+f{NW^%bnk$fgr3~5)wmcB9?iP?IinF_Ip`u$lthsJRSwzXCK^(+ z+oH>B_7gT4%^#)iQNr+#5GV}+xy`BXmzq3PiS_sMGN($ZI@h9lBPO_B6~@og;_+$^ z4>jy)kB{lA^9eW&BnIOxfFc^jFZZWhEdK07+a_9oyIkPuNR>9@&iyARH`z_xHdbGx zrJ((}HCSZpkRyLs@}J!7h@}5C9AS3yDuaAGBdYa8@xbm{uH^YwyTH}sxBPS57+$^3x?$Sorr-VP^DpimMY9?mrV7fDN|qYX-7AKIlW_EUn&i2TQPU`ol{Q8T_j z3Y`Pgx*vwOo%w!V{TzL9#lb5xN3=FEENdz#Z9K;QM=pbpj}P|@B92G#!hezv5dz?m zb#Fx?9PV`%&TUS-Cfs9(7)V8x>)Xnai0pYvxU-58ZOnk(M)T|vD=;Fa?%eNwrjk%J zaedh&fi*qw^a~4;ImiVg`^ew!3XcD;AHCQDEIyEr?X%jj$yoerU54=w5b)|3G~U%l z?F~N~B+OuSuXeqvq=<>Me(U8EgJ>t#t%FiTNzI2s52t85yIrr3E6yIM$p}a5aifg5{Hm-#vfs zfvju4;tw+-nRNFS*f-8+GM=hxhAv;aE#e&F&!N5#4BTu6-)og~+hfWzA46#F@BuE- z6&pb*MIF9WgM)$`cJaMO%B4#6)NMB^*w78yl%CJ-`1%BTJ%;}Elq+?xU0F1uPeJh> zxwC3d@}VSC_%7*YHMm}G2v9Au9F_Ymt_g6_?dD1~S1rHyg?~8{&zuicQLX|#Wg{1JoLuseY7%f>7qeD~8XlpNnyxro+ zy#-1pw^#!=w(2H9-Qx2S5gh;zPRh3anjDe4Fr*#^i%oQz;*&DFxz3)gIP-Dn8Li!+ zZ1)Czyte3zt+jariMjFn*L-b)REbE38-$aOX{A!5`LWxl8e9%sYUEB|cnf@2d?^QPkz_3-j>>sH7_jS zQzLgc1o}RDk}@3g*`!SW#f-xOXPXdkC@Ta49P3Ss@^v0zhktgdP7dfi%{-t}L!sxL3@ zH-zNR!4RjQ6n{RAxu%PJpF9$sKHZhs`vnF}&XC&er?yjjYpW)Aek#hM#fN#Ju%?yj zj4bhw{M*aJi7|>dx=Q9OZr+IfqO|_)Y3u4}YtpkwD|rf6VDb1NL^*~G9J-hCvkb@5 z*DxHfd8O_9UpN{yK8{Dcva?&+xK2)Rkm500&wTlB_PMlb4^Os#MdCbgl{J&}jEvxY z9Y*Sxa}xd3Sxjb7#uDpHgFKLcV0|IVu|_v!4;;|oo*Ls8G?M>u%KK#>VkQml+G}fG z@ZMzKwZ+j8;da#JLXp;p9lSD>{BbH_QND_2VLe8ng%vM-5dS%qRGL(HSFga@I;Jzkb z%Vd|rqq&AbeP_L7O26f!smY~er(Uu-=#N#G@85`QU+w=0{EY0$^CVz@Sf&Y z7&3}Q!0ud+rWb3+ROhAY-?MZ{da8(6Nzv z#LZgt@6Xq#D&-G4Z5N(A{e88^sFNGQs6tsK*(t*ZLdKu^yxQ^abf^F?M|u2kOkj3x zI2uI~qecI<>e`8V)OD+!Gd`GtRzd^@KZ!Tri~Rc*2=q_<&YXeHiqhKz%p$NW@B04I zfn#(dA@i`tOW_p&yN%Ddt}I`LvnTw`A^X*y6L!o3;GES4jRpRObp4MU@UAYC8l!uL z2sTc7F&vqTsi|oL1I8CTh$+p4%l=cFVC3%l5~3??d~cRm$81xgwhPci0D$(Z9iz7ZdBm^=`j-r8 z0X5u#$AF6P$Q{z$1WG)9tHXKDIhy|X3mc|c8Q1+R8orvie2yNyrA0k{M!^U9rU36Ja9~|Z{sQgQ;u>#K^^eI>hkm*4ikU*q2k-5gUhDO8y?*wt z8`~J2E&z=f=|C_cjBNLR3}M%9JM|i&G?8b}hLK4`K!Sirp)+{*Zb0xt_zi@pE+wF= zDd9KT&sXXQwp0A{%J~0MIOzKX8ai%xFS1r46V9S(W81W;)WQ?k6-B9VZed|E@k3;# zPBOwBB;j_F&x!nTU_@T6rqj~)qXthJa*Eo|uOzlt@h=O1Y8pECM&ZaK(#f4wA+##2 z?Pvx;MfX3rH<>iuv7kPh-NvimmW~&}frh$7y^V@*7E>DcA+P+AF>6Bngm<@{m6b2C zdDFEV_ba>FgRAbc_gc<(MH_TxdZOAIUYyhWvElu?9s48mmJ*PV+a0DE`z!|NMa)s$ z07sMP*L)U1)uZuxy$+gdE4dp=F6N}Nw!MvDz9Ajr-1sfSN^a@PpvQNs=Si2!s&E$2 zc##W9jOIvW_P3=~O5woltj)~Ke$hqT|NOMI4iqNo47hzwZsp3ZOM-0QZk(;}AgH>zVczu1L*R<#;M+d}@^2Dj;QY^hr>fB>an$tmIYk#Lq4#(_2% z`Uh*?*~9x-Pj()rO#sUX=0THm;AneL1#lo&b@9KK2K|TTY-27NY&RD-Yyy&{loZVW zTUFrQ)LC$KFe9PrX={Y9u!FU=pyT=4Y2A&1(>0K+)fw$^2V;AS{reW^`b|_C9nNUsjAOO^jJAQI&FD|gqKQfa=97QSxmt0v&G+-!KZf3Mog`vN7U7`Z z;?)=LNhE8Q_;8^9g6fq`n;yD;S*ud4i1) zz23TJkcbJ7aC!HrpK#je^f;qOe0JM%?CJGH6WZ;OL-bVD)*1)Z6IT#ZlzX7DE@XjlV$6S@?dF?|JcK|t!@n4X{_ zOE7LAtZ)%Tpa;ArWH!vGwSN8ThI8-w_r8&fYyn|s-Pp*ae>)`be%b&*=pZ}{5D=`y zVxYhT2ZZY8q5WdCVupwy=OjLzR>}+FGX&%fj(H<%qd+)PCHNIQiaaoRg_Xs_EMovq zo^SE#!`Z+;Wor<YLpbd!iQ!S$9E`U&GNUdXv^_<=I zZsY0okWT*jHT2??`NG z*ASEs@p|Aa{)B=m5O;w1Y^Vj{sS!SifHMkWAg*UJU>`6cfuUOPZPrAj59;7CQ-@Ru z((rmg7Bwxz>fm4jUb)nK6+j{k-YdLj6Bl&O0k;Nn&MaIG0nm-|_c(lx>yt5DKYShP zt9wO6`fMM9ZK!ka4+ytII_6Wiz7NjRD zXakZQ1)LN>oseaA!Mj0&8qwhF%uMqA!)u#&8WV+TFbfH#UlLl5J-B0by{OI;#v7^= zXlc<7ohs-`zk*f5DYCUgV}`QuA&_u^uev!q!{uH;q2q=1CpDI_f}7vk+S`~)rn zTLANau&>V@6zBv>PV|cxlC!*y=o=B!UltBMpKaj%NvlJm&I#2MMTY~Z0yWZZK9hO= z973FbY&<+il*SO5$Jpjlb|}gru};-qzXb;h_GL}i+2o%Km(5&J(;s;Uo1;Z9(2i5> zDR}bDWgV?kqR`-3&cw0xo+~df%wJb>`)fX_9e2U;FwH)cx`+QXF0brx;{X2C3P<02 z?(V*`*H<@6>T^X!(7z|3pf=K$|8%IAm6O8^p>@xBOH-iXKFI1{fvOKl_!kUq%>Z-f zj)I83-BCP@h}?MT5>1CPGA3=2zN;ZR45Fk6lo96kM38%hUO5C!MWd+sVkFQrWb&3@ zR~9EDV@h|feneGPe#W2*r?d2|^TU{q|Jy1M0-ug_pdIQN~Ye=q~m5NkY* zmE1Lj1ki#@K9hb1{19A6Zv^H-pxJT<8f$cTZ`^g@raP(r6rgt_?QN*pjR|Ax8U<-K zc$GvU=xlUOPEIzY4$lFp#LXP1GmGgui0MiW+?s7@-(0hJ6HMYg+X*OUAn#P6(6nC| zxmdF^QAd*IY$Rk6z5m=t?!_oBkY%SyX}2R{w1qvkig+9p2YyDh7rA7|l$M^UjL_M| zQ$V#(5E6mD?@ummXPY25)25bla^uOo71oxTHN`}&;r!^H#7VY$AmONVFh6$i+aXVgd8a# zXG{}q>P)8V;DYY!G+hO3>kp9S1C)P=e|lBrqMmwrnm=Jx^kf=>D_n=XTnAqQO46VY zdtX-%ddb+(#_|6)>W(AXnGkegZAMM8(wdjZ-&<`H0NDmOT3K zSgf=y4>>!=Ir@HG=j)OW?Si+%Y!2iM28M^{&J93fu95a^cjzHP7H&&*CaV(-KVqT$ z6fJ#mIFn$`zUP&3={-X68-zY}{Od=goK1Tc>&`Q}7s(@xC(F9{`NHCD4Huxab30nQPt3PD6XBZY zD0JebArPvTKY=pNF2{Natd%~xoC3Q@J0CoD@S3%>PF+Y{^t4hhK%Vf|9^kSVbrMXX zpIs?+HYnBL&vui~5p|HR^FYJ|@j0Nz&RyN_1I@}>q9LXHCcmr@rt$q7#*rBt^rliN z6FF-6?*KU=kCYCKsly%B)@at86R<$JC4g9VIEuf9lA}HcMmA}2OJNe%He(F-dhKxv zg`5aNJI8<$)^gOk)WbN@*Ek3Bh`wY}RzZjiJK2T5fg?hZ=@Ps>Y&b3chn5%=XkaK& za6pYPI>Fxn0b1w+v2MT^H_+4H#0->aXvDq9q)}@@(QD9uRWj#)4}gfMr6NG%thF^e zv{dBe-oPgyF1c41jkbe$EGW!BZf_p!3aL_p=mZ3l2;ZSo1t=@H4UR>c zcPlI#)~cas8m(aDE>m3KTcWjzcfT)MXDBoYcZs$v*8e3%$g zj0Xcyo+9T?0v}Y=mrxzJl7#9n<36zIXnk{UR-yTm@?7Jq-ih@1$%A!unU01J2vDm zpv|0~?2Y$JvEV{z{$VV_hj8vYmmUWM97eo|CD3e;hT#WoBHfi}-ZK=!PS?SPp+1DK zI*G|;|FlJ>4TI`M60hASy4&FD_xnvbC2C^jNVVieRzi~zW)7%_ZqZ5R?22t(|IYpy zD$V)ZMeDb6jqZ2#5fQ8xB4=>iO$r?bMX0$02$-9n7Xyn3*#aJ7D-?s!BoB58mIX}y zMkr_@Y68vh8{!#UHI(5XYb)}HwPsh0GV;1R==|BpkD#b`<&d8<8Eo@PL{vfgB@b~*OoOf3jmyDe?N-wax zjN$KTl;mHE9YEz}~Fpb`}brAc1R!~5K5HvN2v@|wVZ&rUr ze3DW9=7y$*+t7@!Z)k9ay6p2tr3%}J8GNo}UV%y!LKP$R`sF>2WIw1}lm?!nzwtHO z6o1T&6~kEs_@5AUpcwqAYw!w|0S9>Q#8pJ8Z}8*@i@;P+^}I9IGFOGT$8NU{(fmhc zVcdk4!e8RM!e{ckQEJeklWl-UO>2{C%tOY;I&A$m{8SUV)Ldm*`VLDwySn0Gx-jBV z2A5K#X^lz{uH@wXxrsm86eYH_&0-@P7T0Sw!3rJuh9v|}7b|6FD1ZDm(W{>Q`5H5z zIM9Llf&H2YNsHSyq7z-vG1w51Vd{u25$sV4ARi|Et|&!g1m|uGkA*bsqry`85ZLMm z8B*Gnnnj@|?|6e~R$UjVXz`I)Hrc<86@tp~Y*0Q!05&9X7xkoRm@KROWDNXFrlNBW zR)$gfHSC18Ktq3#mjoP(LeB=p?;E^yd@v3IfvlNpKGf7;pphlfBu~Nzg6zvvqmpd2YjKA@sxB_DRUP!ZS%-O_Z|B zWIE+a(#!B;ZK>3#lcwpSgXUSjk^^2<>O77*GL1Qwe%HqPI&%2WPrG_Pq14J33p@v! zQWk{5zXMkDe!kK=uw`ZD zpL`ay>YCwKQGRRWHQBwIcGa`tli{r;zC;FVzDoPK&1cjEp?z{^ZdsXdOr@_mucM(E zvw@%~pnTcyZEc1nM6$Rol>+CQ=!`fUa%SUKwpT~(sD!1lGB1P(cSA!@KF1Ql>e!9X)AP^Sbc4j$-`GbksWERz$ zFpzc4^ViMvHymzD6kq?kU? z+w+BZ1>tLWOuo6g@G3@82XhoP-x?uJwF|h6_s;nQYYS`gVbn7dsU!rwZ#hRjMv}6) zTj7WOmeTd4%eUPU&N+)o^aaM)%F82jMWvhtD{fB$rFT3B9oq6qR&0ImG?}&NqB=JF zimF>5tkcdqdt8@;a*YQWVqyFnpxbA-+MRh=D#x+wvM(X|?G~MYv=x9O1TvtIuS50N$;f zyh`B0$LpH}@xib`M z9=K*2Z3K0vQk#I4KmgXC-pq^u)f{bfVW9#zJTX~iE9RxaAk9>t;N9~s!w3C0smYg| zoNh_mV#3|Eec)&|ltp!Y0l8NJ5(8UVvM@cWDpUpe{m!FQYf~*qeFo?U`lm42=+9%) z?&(-IR07P;T6-ww{MN96^4WMgc z$pGM2gSJjcr{^74FdX2*;<;m=V{VtO2FA@8Cb8`jbR9^h7jy4qgBf zUjW!!2YwrKbOb_EmG=iWM}oSPD&{0t_l;+XM#Ub^g9p4{=h0VPKJ^Y^rP2*iyTvSk z5shc@-0<)g6gx5ur#}euE)O6Ul|u3&x`T=lQOmmg1bm0tZ)g-;y#r5d7Z9=?#Zr(A zSErne%OKitLKtOdYYGq1?_q)N6+}#piXc*1(b#?i!iD(ipK2eX$rM+pqt5-mKo>3= zkv^d!VI|<~MQot-MihOJa$E)42Ivkd5K{=4kzv)V`Cx#ZtRD%Z@WBA^ldg$M#R+8$ z4ERaWC31g`OZHD5`BEZ~-Y07DIzKId_wgDpyZl82ZlK32fC!0vgM{W#p@c$H%^moV zM3j|>D`1|uaXxXs5M>Q?ckcrWhAXuf5VPH1;C-0v@T;ds5}>QsPw?{aeq>q{y!}1- ztFpiuzcC*&0L$UvGt8do-^44h{_SOopBY)ChY*CmIQmjmRX!wPF{zLk=)IEo4U((f zIWpQMv9bBaL=7Nx(1}ziz|f@GYKX87Xsy81FxnGwXnSL0^uj)d28rXQrbtgKk)D*5 zZ3h4V<`fAh5kTA7kw_gJL^uc{v4JcPoTvvl)UcBvs%Z*yBGCDNz~cf8BhnTy2^&b$ z<7@X(-D%MI8a7;nwAG;p2)RI+-3{l6FqFJ1<|BE@n$bV!QDZ}o0O+x1R9@B1yjaVa zjgn%EZkin_`b)QlOCL2X(C}x^aV#%xRyV8QHCk<;P(u1UWqMIZj9gsd!U#O6>a#UM!tLJe~JXjrB)M$y`0RSIhri8NxKoQ&ge^dHk-U(eFf)EHc&}nlU zZ;t-WKL}Ws4R5P8NReTGy^1awt&NKVH~VvWd3aD0z^ft?wO;(fGT=F29<=4=mgflg z*@B_Q+JJNth;YL)$q@I{kOrNWfTlq(XB=V^Fw$1UQRBFH0IPAaNNS6V z3*!xG`_=Ra3xKD^c5xtQ!nMcQt$&)AH;UdG+jE)Eq_#fZ>eY-456RX7CRylH`yjt{ za<xnH~m!z_zC7?vZJUpM~@B#~_<&s-0IJrCWX3^>=!y`lx_6TgKF3)hx~a@ZXgI zI?DR`n@VzEBaI}1kVti;Dy`+)U{Hl(#hdF$#Gr-i1p|chQ;l6%AieO0693_gg~%88 ztlgI_!*DrJq4F2=q({6$fsy`}-}IS}osk}9+{q}e1&r<_2|FNsO{A=g?iBJvHFsP8 z>}+B8R%~9T)un!(c*|`UD0Fv=Hd1HFJ?x&^5hPztiQattkpCoa`paHg9T=` z^s^KG+;xm0;KV>f`(MQe?IE;iP>{CteNn_$o4uI~ROoOhmPyFO2_17NUX2tn5V4f! zEpiJ|#}&UqNYFdnz$GuNcCW(sJ=tEPT#?N64sSLLnyCBe!EEoG~IAU)GCl7 zVf3*GF7fN(=3f9{fNO&TN$Y+OYRoZ58h}L2StlQGMhJ`{j;DSR5+1?`dDbXcI7l%c z`{)-jF&RZ_P6G?G0PkHI0!#kO)WvQ2vp)ry6Qt1~fdcpm=(M4P0eH1j61a$Bj`~|M zxWg2cracDfH2n6Ou(m`Zu}AR>`jI@?hY4L$7KYk5KR=(5P+vHcC$6e@Y*T?DNhrUH zc|oFrn=4K>;x;_WSF*4Wj;fA`}t#-^kZS67R^83S34)Zhpb4kH~em{jjjE~`1P z-U7n=wr)#aI&TnnOeX$q$iO7t(wM!HloE5=$MFij^0OA`#Lcowk8yD5v7`(FI1&1V z5ci)6C)`Xq`VFjk7WeW5!>0*29>Ny(9vvj5eDO?x0h0_+D0+rRg8oR=Rlr>el!QI+ z6w)qP9MNaTD3W5N!$e$+Id>;I>c}j2=S|WiE{8~R<$b*%*~U3QAo+8d4PPyY(I7dSmsz+kSip* z1%kr@N~U0N#_Sn~{g2XZsWnMS}%Sl{x%=e8l z-foBv3_lZzJ$(271MvRyo#c(t_9&Rn#!yVX#M;qf?aFaMkT!m<)GU~R#$w#H%2?&KAccSW`CFhDjyn_QN+9A{=jJHQ)#KOd&m|)Yy$Z#L{y@ zpuMs(6s5<8tz(E)t)w^&ahvUuZY)XnA?-o~3g4_TfD|C<0z0-*L(Zt|AtnHX;V2WU zG@G<)m69%rK0n_o0m26?WC$pTK_zd?0BVt;Y+LEBw?H7mY}nV$^d;anXz4{R>_Ta) z8PWXvA;8%iM1fVr{4)P zV!?3L0FhUKWC5O!uZaS}^%K#P#Kth~BJCyQG+8tT+kvh(Z&)p&gvU$Sv5qPstci=> z`W@3kO9oc~X)+d1B>}Y<_T9&CF*9T$Y{AWf6<|kDUU?O2Gs+Yt<6!idO)CS`%w*ys zVu+mq5LVkII9YmP_ginf!^Vg4C{J;+us;(@w@|IV41$Kl#&ovq;Z+fYbn2Bj5j+f` zabWi4$SoT(goBbm_wMPzk?3qbcmZSFte+<~J;xn=f_}`8ehlas3HT?7V5L!-)aSGG zQY^V_b|X(|-T>SPU-k3iqi7gS?dpSLks44Uix_QtK=YiJvl@?%NWO+no|svL%vI=G z8DtwFX`DuNVSldkKC4B&V;KP4T(3kDDU~cMK5A;H<@6l8MF#Ul^#J;mnA+g=@X67K zTSn)1V&_Pm#W__1o6>tB8|G^wdEi)u~VE-Qp6x2qSmMc_cE zw8}57$mqv{7chQI7C0*u4N&*Y*t3f`#FfxS_8X!jrc3v-KTrhi8pL%<-IfHJ ze1rX8@E%$hF1TpUGhln|oU%@x6Q(!TN78#7o4RwuvrLMiMX^Cdc1L=|4x&mG-KyWY z9w-b&kj#gGF;Ry3b{vTi!y#~Bwz8y(GG6vNiVFLel^CaqMhK=QAp^!G#$lfWM znC!x-cUZ^z+Sx6Y68|3F{axATi|Ld+b+&Au?|qy&I8BFtI98&OlS3na|6tUX@uV1J z8-T#NQTMycKdfRX!Hv{+e|yUlW%BcjP424bqqVAfF93kHcjoNArcG!NIRv7VJ!K$m zJ#?F&b68U%&KRFQ$fUZb&if$0?y1FAKJ`7zwmJz%_;t!NY~yXJ)*3fQUXa>Fk|En> zy|reuJ*sBMF~U%TTj~rX=sMhy)ka7EM>+9o_uCotBWQ4^Z+y-hm;!-G{wkknkZO_l zRI8*aA&tK84Wa0~g2dby5=qhGUIeO-ZRru? z&pW(d80%iU*B6jsR;GOM1K;;Q{+SfgmCq@TES)UaI?V|tzy+P-@qHWyNPR2zrp(Bp z`~X`w=7G%aVgT=k;kESTM0k69Fk?-)^^RHEsHrE9K1e8U)xGH)6~uA(Z&%65>#k5T zyM_D^?W9EEA-Va-4^-0z@EBm7l)_KK!QfJ;Cj=Qdy*e8+mib^a7+%v|HV(0Kj<0q| z$kr~4wl`QwJxhG?1I2Q;hh+rhG?b9UT4voD?d=tEB=h`whhRm4vY~mu7aP~ldjXmK z)G-W7h(@)v$&ZMwxy}JSlj400bA!5>+M6NLAVHK#&rQU|J$V zc;xn3b8hfF3_-9iQG&rcq4%09{FscO-7cZ>k4YdlvMY0`%QsE!H4;-ehJciPVfYHw z(Ibe;;MCWPaXOkHpi29xdSV$g0EiDZ}ob)u#<;uYPJ!E1WopQhvI z*-wWBsHxE%u{`)<$xsVcvY)(;aT}T2Xj}sjurmSLOkfIO(v}~fVA3=QTZbc;<<2Er zi*`V;aD0*Nv!#*tWK{eZ&P}-k!endcKc#gRke%+RCO}stvG#B(_&@AJ4_HT{M@4=5 zB1F-U3ZOK-q6)qTsnUR=KJstWCcotx-RRVZ_tm0X?{y-}`a*WoyQ^3Jfj2e;ET1$Z zV<-O~F=&7t44Cjf%FsrE!+>S&9)q&2)W)JV+FH{toiXbsz*Jrc+HjIty{=Mqn63dL z11;|3AbW|FwzjtqNq^Kh1(4E38(jJqqwak6?w!0ISynb%_FC3DT$4TvKdt!6h{CX^_=X~<0b86KU6kMZO?lse z)oD}C^jqBPs@v)tral_f7{Ne%mhZ|ts09a!$?i2n8#d6B8>s4{awq#Tl}%%VIRF`e z{@I9xFm26uLFv?7ebcxiBn+&TzV>BjRuuYrvd!*>0@fvN->2$1UAelHltaTCYx^8>VJvHR&p>9jTN{ z2AMtV+@|h&hMhlRyHn&pHU*;IhpQ4uO(*ysu;O3jS~!?sD1c&w$~ceXB98I!_h%U| zUbtZ6ZC~s)%g@2U&$ou^>Xju+Pwy!wAAI<)zjAN-VtJBRoVs(N!E@fq=O?e+6V|V{ zGXCvbw;VWlaP$6ia__9=(_>XZBx3WiOCEH9)4M z3Wk=hw=ISt>1WlE8N#S)vV?SM&z+2$DnOj%YW4TqWA5-QchZDk-+jIFiZsOP077^j zM!Wk6@WtPD>CH$Q&?RsgD~Pdi7@q(-{vZqwUMIr@SL7RPQKJMQ-X(2ZX-XJGaF$Bn z{}R2JF~>G6{b5(lHR#L^3QoCJ3-QmVfq{~pAD?hsMGR6Szk%t8`-Dfl$mY$~8wYDI0~713K!%qTQ)`&HQo{$m z)g-Ni`TgSMx$pxi!@Ge@&@=R}dI1w@`4&NL466ze$qSm{e+c6`DO-7{ zQtC#@ehrZhjq2S*X7iXbI)1{uHJ$Wv$YLh0JR}(ly$|8E6sI$A>_Hja7;X6ZUXW1P z=_vjbQ9)-W91Y<%Av$!i-b0n0 zVyL3EDVJEwhaCTv$0JJD9Xg2G4#M|BNI{R-S`qzyO2R|nmyKS?4R3(+1kYiQgvdLr zza=jv=BGRL+O?jl-FswcFJYf7%ky=#=oxFlgFl9GUE_%CoQJ-Nb()S%cR_KX!K?Jo zhI9rjF`Q~}Bn!NL6zgKdM7J+M%}ko8aNpYC{pY0?CCfpw*W{K`F8WoFm?!7RLnyRI zotOT(QT$kN5SrmnJSzKtyeYr~j1?uZK+`bViRK^w1`|`8n~{R|&wu3{YV~;@)fFrO zU=a~P$zvcXo3Z2W#Y%Is;Q2|jb|lN()n6$;z%ep0M%^ZphE^QysFq()p^jta48v@~ z$E5tI&7EIfQxo#9jSDK(Z2ltTPA& zYx>B!Zuqb$R!%kG>7Ebcq-}4!e7S#As8RYNaM#S2e!D!k@<;HB?gxkQ$Fm=22>af@ zi^LmwIq)f(^o%GtVk$JeVoIszxJ1Z67us zq9T6$0g#Pudb*8!?&(+hAvwlu3oEP^jeO;W~`4~-Q0 zw=uPr&_%NiB2qYQryzJK`V}#VyxOA^1-jta1HI^B58_VdqQfHill~Jfl*?bCVCUH6`n%z>&E#u2G!NW7hPR<&;SG_l2rEC)jlXc3)uWg zR{~qmM1@?E{2zG4E%gh5yh23VmMt*Vv8<4l#!OLR0w+jg)?tzs0Efs(76Kix22ykQ z%e+IWcx>jT3-jU9{U9G9TP{8chPZ~-*wPhLW$lvw*S_z8H97jiMn_%^uEkg@>PFuq z2$$auThFUAqfo+I>ynYt4m|O_#8s=Z8$1RAs8Zy&LHb+3G5FvCiw2QDT({uP@8Egl zL36PbE4&=tK>Q|vUypMKFf=$ufccB*NdS6Q9iwB_w%=bQM!n$m9p{mm-}fBPyz<=@ zT}j|dAjHO%&SZBucTU)`J{UfJLYF8)P|9XUk)Zzi`uy^+8OXO}<$xu5oD=^rtLIr4 zQ)(OU!f6Wq-N>vXAK8}=Mh_J`VFxe;fxJcn=IT_b z>TI;sdV1D)aew=g6jRzk^~j^wW6x*Ps8^sW>h@)8!d&R)QQ(&A=hY}L&oy1$jd1Pr1~jNOYpukIO|7$fxG$MHT# z6`8M*Vk#>xg(*&T8^qcho*MohU|J*76{cMCd3>xZP9reymVL~nw~^@e)~0tQd_0Hi zy6eH-4#eF(BqZT?p(ES)8|;v0KK~Gx-TI&d=o8ix~uqZj1ob0 z;ou6w4U`{5U{>-cj50OG$>1`j4?o)~xUij~Khmn+S5&BKr5>g`w;WWDl3?cleg_Aj z@2rZV;LajV7dnx)j0nH*B+!!ILn+4BUTNw<0zPeP? zl&(9!9^Kcgwhya&ZKcuorFEo*nVM<5V)r{vIwGYPQ(6da>SqhmU_nu$`RPE*wco00 zQzy%Py_7wIb3lJM9&d$$AG|BYuZ$d9nD{wP=Y<(ZiYj07i(lDYGHBMM3@p7O96+rz z8K<8#QR$RdplQ4*(%t-~Yt3s028L)>P79P;b2ni8VjeI~!S#8FsV9V%P%Z)myBTxM z<hzzi~_vw&c%Biw3)a2z1j|MW-rV2#U5Ct?Ir7YI39!ZN;-#2N1$e6{#lThqXA0bHX>A(RYpRN9HY7U}PBj=SaKs z9vO)tAqM^XN5D>sXXa|6Qu$DoM)dw2mmeJygab4p6bB0Z;SRN`4nF35D+;! z(eFV`3i(NflnWkbE!{>``Ol(w3!ilS=_$X+$S=su)R7XSr35@Qz>gIG2Mm3X;XtIs z9H~F{^egz-*Fo*c)jf;qUk5}i6Fo3FVQN)jtDMOFU%@1$CcqqVvuf=yimFoZD1Cjm}5L4pY{-2l81 z%&wOhtrC|;M7xkhApd}r*)DV@plpDuW*m+dK%JP*OZX*}c=7xg;e3zzQ^{8p`w3w% z(O==$=y|l}Vuz?&b;I$)?<+%b6Se4-WzN;{SXuzN2UUOVTVI!!dO7snGKvh1c}-4C zkZBD_O+{YnKTmtA%x;zdRvTN=8$^PL7DCkXlfg_-8VF?7L}4FgB#oOys}Zk4{2{ z%QkNJ75F$p3#zKi_EQL}c_#XeE1M) z38v%B&5fUh5h)573#xjm|L&I6dN2_p(QO$U8~gI58ww4-*S`M#Y(C>n3Rv;rpUe?a zFgv0Rhk_tp`5Y?0pxB{pAf1Y!7uZ+*Z@B;ukSq~iIFb((D~pPXJiWZUJUu@{MX<99 zxC?q&-r!22Eo>OG)-S$Bz48dx{TiSLwtvAm1Yh6o7jV4B4E!54W`0~8+`At?ejpEO z#%QTOzPW@b0W1P1+W{p<99z_^F-of#B^0R#0JZ=g4SjvcA$FY%h^G${H;V_H-gpqo z=Pyr{QG2E1@1UOUcc%|jFd#D!)2qb~g5N$mI!c%u2sSP7T1XslaS7QQDF*J#&O#gDDeuMz8XO%X{7NQTf01RwP3*=v5 zL^1}2firwb=5z9)6Aw;-PHwUi3y^CT|H z$--Q-mk%J*QgzwP8AlV%EKcXT8P?l4z>nsyfnJ?O+bJKBFBK8IcclYxuLmON6n@6> zMPaf)IP6$uJK>$6f4{xhQ!;Ol?Bs3MoUia6vD~Y)j|Zo8Vbzr0@{5^C*#)#7Kt1)& z9TObr`H$KPrm%KL=vc_UwkDMDy)7O$_zhKn9qi|lbrrcdQpkAx_?TGO)7SY!z#_1>eY#+m zjKV-$(ZmbMQLufV^+>f!8@#vg2f&%d!A06oUu0M!r)U;+Q(j-qoJtpw;j z0OzrZ(hMtxVBsx}3~+XgbwZuLVX7Dg8?u?0Dokp(gt-UzKp0XW(cNwo)`6PQ)Rc(V z?$6zD{K&>oy(n}a<@=nHT&I{)R)2oF??~u}hoRphV-y3>ZdSFVv8hSt1aVAvAgLte zD6S1Y_V1-6jNCGOpf(N$m)D7%v|hJEKwPqKhLdRR+);}R`g(eBZ0u2BA|Sef2L2cH zI+#nO@7eWfet2YqC^gca3;2PwFF0duaP4_PDMV)C9>Bf zJz!c~*KROWh39d5MzP$;rTQ650Lcaa~@N)#MD_*%XI2jD$GDEfui0J|;cHpq6=_Ag9$ z|M6$o3D{RJF!s{nuP)(lakDc7Gb=KJh`&HChv>@!{n^O9tbyJjYnf8R@A7!bH1M8X z4(K{*%a-QOqkQp@)V{a-8rqM+Pzbn5Z)cAh|%;g#pKN-z5LQBV*ZsmEj1 zKeJT3M&CVj&%peaH0O7mJ$M7{eL3(HZ0`NViMq+2d)MwrH9DsCY`DE31-1PO+rf{7xo+M&T2ntuHZOffS!6iH*>@sycacz=t!y5@PR^GyaofNQ2 z@)_aXQPO|zx5APnRuQ)Kw$S~6jrb>I;c)#pt3-`KQn*c~|K-@Hv{nf&A*p?b4uWO# zd_-}<_RX)y{)L5wq^Cmdnr7+_>E}o|#OvFVN%g$BG{EsyO93qeAHKoO)GtF{b7O zeMv?AapjA$ft}_ccyDKpFvhij*&yQ&GgB~DoR?EmL!%1FP6_BNuyv8LQe*Rz=S0;O zxtUSzyS-hU9Y@gQ$r-&HKVejA*4Mv-wa_o<{d7kUv6Sl*-K{~hsV5EW(Le%T9c zb5%#Q&0St;>3ngFZrY!;VEAzKcEli}M4hRragLf18|Xzeq;MNk~Wt2oNW5`t<3vv@}q~)~{cW z!qHh~4GEO9Uh@WyX;Hi6Ph?|G!AS#k5%GP_?8sQWb z;e`DDZPljScCalzQek-x<`y)*rHM~_A3k`{KU*%h(Qr!04Ux84IYLT|Xgg|Vru9&4 z_wL21COr^abr#~IqLjAE?%U@K|8HPm;7VmTY0H7rtlae*Ot`oJA6|sMvTPGlHlQq1 z#W_H6WJ1)|T6OP`prGJ$D&sBu{CZ4|7=&3HP=rZsiN5y7QGP=9k5L9miHTkqU&rq= zr}RPa&UiW-FVd(Qweba>Az@MRz^y=rGSs1lNlyC++$3Mrb%&u7CUp z{B@LeE|}6j7(nS~|GwS3kMx%DV1d)GcHS7eDq02;u<)n5d%K_u8m*{ek(!mGd*~!n zH7vB#kSP23(Sy+Iwc@K)dc#rl@ZrOl(&txaf4Fx@T$=f7nmdSgXsSU?9UU04H^l;G zrU*1mjvcF!&@aO|s(gF6r`8q=i; z`pMoc|8p>RhyVR}jC*hG z))3dle^n^G%g_JbY|6N0Ab?r^r_Cx6H1)8l=~jL{b2c+Eod4e+kt%3u%Aos3JUH*& zKYJ9DktY1%(2_tG@!!AaO&Q~|L5Xj^Pbd;}yb=5gbzL)h3QPGAVM3zOae#0u2FOxf zH#9QB0J;$L(Tsf(L}R5nbjO!ZwEMLZNdJnlvGGp9LL;QV-r`Zi&Cel3df{GPUS5pr zVQkJ>EnVFa$cdNOMk6N;x_+JSfRo~ufOUmP=$2-?^nUE?$u@8ovyxNA(}i<$V2YgJ z5A>~+R=omZ55MU%U-&i4$8>d*N-eB#>f3aD;?cZ}KhxB_8Fo#m0J-&usVfe3TJ*-ZqNCr3OZE)MMh#O@{d8CRkL1f!Gc&c4Uo_}17`eIaR}ZY@yK+P;)!)7A z>K*RYhX2*=p(;@r9$dr|-)8NIKsqzNCLa|C)|^v_%R3H8`tw#20eeA@r5yP~IefndTK>#xntSj2#?KJ1#{(Zc;;^GWi* zzQO0EfhU-o^SEHHq7oS6TK;f56{iv+4z!kuNTxnQSsBO6ud@&ISp=yfM+?!z76K@Q zUFHHj>HiLL*ePg!!n{eG^$%Hm?OC2BQ(mwet%|qjKW6^mdu*3nH*s}wyb>4Wr^Fb}MYC+|PAbyQq`uqFCMye%+?F zQ(g?B6H;laQVw2T6Efc(Neyqj>pTDHB%-ptN9T0#CL!VBe?E#?yf*&db!KsTwPR7V z`sR&ucK${zpBtw>Puz-|_YnVFL;e3%cjf<7?fZM3I!bk2FBvnH#NA{{I!X!|%FIDW zGBp{(-iE_PLOG?(vxo>$9aEu{sWOESxfVn#xFx1feAzEt=4fJ zqI+x7EMCX%5)sklq{5%lu*+$Fr?BvyjGKF`IEa3#s!J%n1w{ujNf+<*yrd+%%iJw; zy3U_%3U7$ah<>=lk!5$8ohK(EdZ+FTVPrhyqhV#wb8&bdyI^Yd{#&odY5xfsUYKv%oeU%n1kW$({6{pe zWfnQsO2;qNi4GWNG)D1l*e~c@sXoiG^!4v**?)Q@@#?k^y}R^m6t9+J_*blhe%0>V z&zF>)cpA@tJ7Q&@7?gS8Hp^ad1>WYv-z3I`vH6RMi$^gz3rM~j;H8}R!h(6?A}7RH z`OFK|k#4NjJMbG0%vQFsY~?v8Cb(l}j>>gozq`HubHh&^3m)lvZI?p2ygd`!vvdAx zS7x6z)uVMqpJyG)8GbtYhuQi-#aqP#1$;Voc9e1H^ii<5_3LDATa=Jy_^^18uy`87 z$IZvL`^O5CmDd;tEFR@>y*0a4Z_e$x8g>6N0 z_6i|J0t1_J9Y#Yt^xxG5)a9w_x(daGWNz|6ElhEMbX+$W1F$9LQYYrEFwp&tgKvIh{j5@0}Fo4GZB5N z(~+a^9_L}RLB2d(Q#R9dkg<@Iw=D^#COc#}cHEgrYm9N7o|%F5Po?{MK!!IU1{d~6 zK%mn4DJ#WSJa`hWECTd1vd;bJB;O1HRb8mIo?hJQ`XSEu7hIJ!3RH?tM($l99|2s5 z1cnY2XzcSb=P@)uu2B8a$`>^RtvQfZ3PSe!$xQ$e9;qq_?S^Y5`ln6_Z4X+lyc^3v z7(_KUGfQNgs6oJ}RR=&)Xr3(>=7b6ee1Eo{b?RMZqw|Ox90gyI1k3uflz|o}P zfnK|qlvHM(>>w3=VS%G0BzpI2s;V-w zvcQzT2i8zpnhMD7c>%ggR8}_1!|SasRZ8bl_Q}YcJr69xTppczPGEyIH8n|y2*&QjiO%^xKWY7$gSS7_bcU_u z+-M&7oS=8#Zc}(r7pK#;D;vw-zI}^uB&hJ`v5~w21g+jkWc)DLl|joTwVN!KtS)Tg z8bF@&2!^hb(6Q@>^zLc`E^gbwP z^S#8>xX=FhjkPtIy1eX+9)4<{;w>%^hu^it-hZp+U^%eI(jsT4o zuky(vK#VR2vr|;+gwo1lGcvpS`uef4^9mBv{Ti9)4r*M=e14#`rg+$gUHQ=rMhcc? z(j*xP6Jg^F@+>GQ2qeM#p&^vV!(O~b(dfg);H@U$VMsX`*^JLxvKJ+G;6V_QS#p{m zQ8O;As8|6!6$UmreH!Zl!a8tq^PRRShL#BHS*4~%M(OGbT7+EAMdpU>Z_cMQZ{57n z>`{qijDvk8D2tH~9~M3pG?(Ky>h0~FHOI!c{H|{qGoA0p#KeSzgy8xBVoxITM4e-~ z*>9QDfIY4B_!}k-8b?)2C|xLVKH=f!uI0S)dLT_QD^J$SFN;f~jGi`X`=R^z>BRB7 zc2AFOvb3~xKX|r?bT4P5A-Wkzw7?0dtEnv>4(7QDH(PdU(PWLnYaKfE4xVV(ZFmI( zihFuZ4Zp@Swnu9h9L)oTHcXX9%Z?A&i-d#(4y)t2=8H9aG@X>gJ#hDZ*bgZz#dGdM z3tivuYjjie<;NdyU6`;3%Se(uh4y4s_LQ+|fpNz8g9i_8??I3l(fvzaRllAca-E2P zyI~tTn>gMpS{lQ_!!6kMMQ2z*vYG9t8y=h!*v$+W+nzt00e?1j=Im?9YVc5+pF z<8twlgY%f)7AIM&z}-pXax76P!xzSjIy@U(djClfsfJ*ahQSY6U7R!V@>&EJh&~84 zO_GZyo#`4H-l43Bm?7`@7Kv=sjT=y#kAW?zuBtNd!A)llQIwim6=^mCmrc|Npv49j z00fGZ6=8~BBPIQml$5aBprTQ>{PiRHdvKH8l2-{XEi1DR!Gzm?`QL0=j!)Q{^Xa+* zZr3(9cg3Fmzxe$QH~0nCc7|=}bIurU%e@p89WBh*NC=5!kB*j)P#a%cM^V?fwT~|1 zd{ZaDv9=&2oloS^2HR=o+WmshR)seqyTJ#qy^I$w>^qD)T9$Y9g*?u^ESit12i06hCxj7d2?NVBOC=(5zXbi+ zy??V8BTpcdy|h-Ot9vAbgf8(t2K@Z7WK&DQ1&1+`4R+$ zhC(Xd37~-d9jK?+g`C{}<675mVPP4*Hnse15~1puyos=M#nQw7eUv7eR|hm{e*Cl6iL+F2=%@9kt*Ku4scG>6X2&c>`A1yVy}Dpg9pskZj$ z+Q+c2{Oj+#NlE;q4;KALaP*&!80UyhPHqgHnVE@Rdv|3~FlTH+LS5(v`O zEUVZB5aj1Y0#r!zb)nx4_`i>MuY8|>;sP5g252FOdh)#}3_NiG&;DbpS$)vh)m2uW z>|Ou)cIb3nSU8G+5M1a8bZd9Rw6oF)byOmp>0G2;1BsjoB1Rq#o?*-+Yq5^As;bJF zj_)VB_9QnKR}GuczI`UhK%g{rrJp`?=I6(O!m4IFg`ywciCH`Te2;A!dDpIM>C}{S z81wb(*YRZO*Rj95i$UM~d=j&{oYiFLqucG)p^iRM6B{3|y~;lj#BkZ?8a{qDxEtL0 zm20wgyI&!8zg!fkk7$jMfSpJ!tyzfnv2z44=q`e&U$c&%aVFfHoYi7NBzNfvQSNOViRw^O>wMHBV^ZDvi41a)BL_k9i=e)+eFlCiO|$%>U_G%uim@?kYsUS{Ui-)?$b z@(2p+MJA2n!5^rOL0N-BGLqWF#6$#f5H{T0^X(^ThFZbbt}$=fLQac!AGi~5iMB## z@VOxEpXb=8LOfl#)l0VE2;;O-_1`6Xx4-woWD$Z-Q4#5rgW`Hw*+s}4#5T-L^t`L9 zvxhtZ&6-dg`VCy+MqYIYOlTB)|x_xjtQI~!hC|GgK+A5;O0B|yBqhCd=%~nSo9seyxx>Y>j4@d zT;1FtcCSbFu8-k5ha3=+@frof^$b5yXt| zf`S<%{X_qOFBo8dYh4{ol6=G5zqXIMxheWjd3x_ikHwYG3l$vf@1I(rhtDHLL7^o4 z&=*tGR?tcXLUaJBXFft9lFGtIkEn0j&is3X)o($S?D7%b?llyN=-(>!TCb%govyj) z2x0`W358`qvsRo5UeN2%A5_)WW@ThJLh&grO+$zTUdp|HKkAE|(CXPQ z^ngR1BMk|p%opY>R>(#km=4^p6N!k&FHUH{$Bx=wB`2X#{ e8lM}aSD0MdTxq^dC;9R12~Bl9wcI1tm;Vbr`Qm{9 literal 0 HcmV?d00001 diff --git a/graph/Modelling with Facets Pattern.md b/graph/Modelling with Facets Pattern.md new file mode 100644 index 00000000..30a3569b --- /dev/null +++ b/graph/Modelling with Facets Pattern.md @@ -0,0 +1,111 @@ +# Facets Pattern + +Microsoft Graph API Design Pattern + +### *A frequent pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with common properties and facets for variants.* + + +## Problem +API designer needs to model a set of heterogeneous resources that have common properties and behaviors, and may express features of multiple variants at a time because variants are not mutually exclusive. +For example a movie clip stored on OneDrive is both a file and a video. There are properties associated to each variant. + +## Solution + +API designers create multiple complex types to bundle properties for each variant then define an entity type with a property for each complex type to hold the properties of the variant. +In this solution a child variant is identified by a presence of one or more facets in the parent object. + +## Issues and Considerations + +When introducing a new facet, you need to ensure that the new facet doesn't change the semantic of the model with it's implicit constraints. + + +## When to Use this Pattern + +The facet pattern is useful when there is a number of variants and they are not mutually exclusive. It also makes syntactically easier to query resources using OData $filter expression since it doesn't require casting. + +There are related patterns to consider such as +[Type Hierarchy](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat +bag of +properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). + +## Example +The driveItem resource represents a file, folder,image or other item stored in a drive and is modeled using entity type with multiple facets. + +```XML + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ... + +``` + +API request to get all items from a personal OneDrive will return a heterogenous collection with different facets populated. In the example below there is a folder, a file and an image in the collection. The image entity has two facets populated: file and image. + +``` +https://graph.microsoft.com/beta/me/drive/root/children + +Response shortened for readability: + + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('93816c1c-1b19-41de-a322-a1643d7f4d39')/drive/root/children", + "value": [ + { + "createdDateTime": "2021-07-07T13:59:47Z", + "name": "Microsoft Teams Chat Files", + ..., + "folder": { + "childCount": 15 + } + }, + ... + { + "createdDateTime": "2021-12-15T00:07:36Z", + "name": "Versioning and Deprecation.docx", + ..., + "file": { + "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "hashes": { + "quickXorHash": "r2d9uZilW0zEIXwycymsUQzhV+U=" + } + }, + ... + }, + { + "createdDateTime": "2021-12-21T16:32:51Z", + "name": "WhaleShark.jpg", + ... + "file": { + "mimeType": "image/jpeg", + "hashes": { + "quickXorHash": "2vHpAA7RDZJteIwl1pXR980xuh4=" + } + }, + ..., + "image": {} + } + ] +``` diff --git a/graph/Modelling with Flat Bag Pattern.md b/graph/Modelling with Flat Bag Pattern.md new file mode 100644 index 00000000..136f0f1e --- /dev/null +++ b/graph/Modelling with Flat Bag Pattern.md @@ -0,0 +1,131 @@ +# Type Hierarchy + +Microsoft Graph API Design Pattern + +  + +*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +## Context + +Let’s assume you need to model an API to manage groups in an +organization, where employees can create groups and become owners of the +group by default. At the same time to support business processes some +groups may be created automatically by daemon applications using a +service principal account. In this case the service principle will +become the group owner. People and service principles have some common +and some unique properties such as both have unique identifiers and +credentials, but users will have additional properties such as email and +manager for example. Conversely a service principle won’t have a manager +assigned but may have an associated application identifier and a +description. + +Additional requirement for the API model is to be flexible and +accommodate future needs like a device may become the owner of a group. + +## Problem + +How to model group owners to allow a heterogeneous collection of users +and principles so that API design can reflect common and relevant +properties for each subset of owners and accommodate new owner types in +future? + +## Solution +-------- + +OData allows us to design collections of entities (entity sets, multi +valued navigation properties) with values of different types using +**type hierarchy**, where there is one abstract base type with a few +common properties and one sub-type for each variant of the entity. In +the current version of Microsoft Graph there are many collections of +items that represent slightly different things, variants of one concept. + +## Issues and Considerations +------------------------- + +When introducing a new subtype, you need to ensure that the new subtype +doesn't change the semantic of the type hierarchy with it's implicit +constraints. + +There are a **few potential risks** for client applications when new +sub-types are introduced: + +- De-serialization code might break because of missing +properties in returned collection items. Even though property X was +mandatory on all subtypes previously returned, the new subtype might not +have this property and the client code needs to deal with that. + +- Client libraries for strongly typed language might ignore some +of the values in the @odata.type property without further configuration +and need to be configured to be able to pick the right (client) type to +deserialize into. + +To minimize impact on clients type hierarchy can be refined by +annotating the collections with OData derived type constraints (see +validation vocabulary). This annotation restricts the values to certain +sub-trees of an inheritance hierarchy. It makes it very explicit that +the collection only contains elements of some of the subtypes and helps +to not return objects of a type that is semantically not suitable. In +addition, you can follow some of the mitigation techniques such as: + +- Avoid overgeneralized base types + +- Think about roll-out sequence + - Consider that Microsoft Graph does not return objects from a workload +that has a type that is not configured in current metadata. To avoid +inconsistencies, follow a two-step process: + - Introduce the entity type to the Graph metadata but don’t +return objects of the type in any of the heterogeneous collections. + - Enable your workload to return objects of the new type as items +of collection. + + +- Allow time for testing + - Inform the clients about the change and allow them to test the +changes in beta. Time is required to implement the code necessary to +deal with the new entity type, both in terms of de-serialization as well +as integrating it into the rest of the application. + +- Communicate the change in semantics + + - It is necessary for the client developers to incorporate the new +semantic into their application/service, even if the change is perceived +to be small. This requires early communication and clear documentation +of what the new type represents and why/how it is considered a subtype +of the original abstract type of the collection. + +## When to Use this Pattern +------------------------ + +The Type hierarchy pattern is well familiar to OOP developers and well +suited for strongly typed client programming languages. + +There are related patterns to consider such as +[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) +and [Flat bag of +properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). + +## Example +------- + +GET +[https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners](https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners)  +returns a collection where each element can be a user or a service +principal, and has an additional property @odata.type to show subtype +for each variant: +``` +{ +    "@odata.context": +"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", +    "value": [ +        { +            "@**odata.type**": "\#**microsoft.graph.user**", +            "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", +            "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" +            // ... +        } +    ] +} +``` diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/Modelling with Subtypes Pattern.md new file mode 100644 index 00000000..73dea0a2 --- /dev/null +++ b/graph/Modelling with Subtypes Pattern.md @@ -0,0 +1,138 @@ +# Type Hierarchy + +Microsoft Graph API Design Pattern + + +### *A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* + +## Problem + +The API design requires to model a set of entities based on a common concept +that can be further grouped into **mutually exclusive variants** with specific +properties and behaviors. The API design should be evolvable and allow addition +of new variants without breaking changes. + +## Solution + +API designers can use OData **type hierarchy**, where there is one abstract base +type with a few shared properties representing the common concept and one +sub-type for each variant of the entity. In hierarchy, the interdependencies of properties, i.e. which properties are relevant for which variants, is fully captured in metadata and client code can potentially leverage that to construct and/or validate requests. + +## Issues and Considerations + +When introducing a new subtype to the hierarchy, developers need to ensure that +the new subtype doesn't change the semantic of the type hierarchy with its +implicit constraints. +To retrieve properties specific for a derived type an API request URL may need to include casting to the derived type. If type hierarchy is very deep then resulting URL may become very long and not easily readable. + +There are a few consideration to take into account when new sub-types +are introduced: + +- TODO add something about SDK dependencies and required actions +- TODO Client libraries for strongly typed language might ignore some of the values + in the @odata.type property without further configuration and need to be + updated to be able to pick the right (client) type to deserialize into. +- In the case of public APIs in GA versions clients may develop their applications to support exclusively the current set of subtypes and don’t expect new variations. To mitigate the risk of clients disruption, when introducing a new subtype, allow ample time for communication and rollout. + + + +## When to Use this Pattern + +The Type hierarchy pattern is well suited to use case where each variant of a +common concept has unique properties and behaviors, no combinations of variants +is anticipated, API queries are managed programmatically with type casting. + +There are related patterns to consider such as +[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat +bag of +properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). + +## Example + +The directoryObject type is the main abstraction for many directory +types such as users, organizational contacts, devices, service principals +and groups stored in Azure Active Directory. Since any a directoryObject object is a unique entity, the directoryObject type itself is derived from the graph.entity base type. + +```XML + + + + + + + + + +``` + + +Groups and users are derived types and modeled as + +```XML +  +        + +        +… + +``` + +API request to get members of a group returns a heterogeneous collection of +users and groups where each element can be a user or a group, and has an +additional property @odata.type for a variant subtype: + +``` +GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members?$select=id,displayName + +Response payload shortened for readability: + +{ + "@odata.context": +"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", +    "value": [ +        {           + "@odata.type": "#microsoft.graph.user", + "id": "37ca648a-a007-4eef-81d7-1127d9be34e8", + "displayName": "John Cob" + }, + { + "@odata.type": "#microsoft.graph.group", + "id": "45f25951-d04f-4c44-b9b0-2a79e915658d", + "displayName": "Department 456" + }, + ... +    ] +} +``` +API request for a subtype specific property requires type casting to the subtype, i.e. to retrieve jobTitle property, enabled for the user type, you need to cast from the directoryObject collection items to the microsoft.graph.group derived type. + +``` +GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members/microsoft.graph.user?$select=displayName,jobTitle + +Response payload shortened for readability: + +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(displayName,jobTitle)", + "value": [ + { + "displayName": "John Cob", + "jobTitle": "RESEARCHER II" + }, + ... + ] +} +``` +API request to create a subtype object in a polymorphic collection requires "@odata.type" specified in the request body. +``` +POST https://graph.microsoft.com/v1.0/directoryObjects + +{ + "@odata.type": "#microsoft.graph.group", + "displayName": "Library Assist", + "mailEnabled": false, + "mailNickname": "library", + "securityEnabled": true +} +``` \ No newline at end of file diff --git a/graph/PatternDescriptionTemplate.md b/graph/PatternDescriptionTemplate.md index 5804ed3a..12ca1570 100644 --- a/graph/PatternDescriptionTemplate.md +++ b/graph/PatternDescriptionTemplate.md @@ -1,53 +1,49 @@ -Pattern Name {align="center" style="text-align:center"} -============ +# Pattern Name Microsoft Graph API Design Pattern   -*Provide a short description of the pattern.* - -Context -------- +### *Provide a short description of the pattern.* +
    +## Problem +-------- *Describe business context relevant for the pattern.* - -* * - -Problem: - *Provide a short description of the problem.* * * -Solution +## Solution -------- *Describe how to implement the solution to solve the problem.* *Describe related patterns.* + * * -Issues and Considerations -------------------------- +## When to Use this Pattern +------------------------ -*Describe tradeoffs of the solution.* +*Describe when and why the solution is applicable and when it may not.* -  +* * -When to Use this Pattern ------------------------- +## Issues and Considerations +------------------------- -*Describe when and why the solution is applicable and when it may not.* +*Describe tradeoffs of the solution.* -  -Example +* * + +## Example ------- *Provide a short example from real life* -  +* *  + + -  -  diff --git a/graph/ProtocolRuleset.md b/graph/ProtocolRuleset.md deleted file mode 100644 index 0ac48dc7..00000000 --- a/graph/ProtocolRuleset.md +++ /dev/null @@ -1,178 +0,0 @@ -[[_TOC_]] - -API owners that have onboarded to Microsoft Graph: - -- [x] Preparing for the API review process -- [x] Designing new APIs or updating existing ones - -and are looking to do one or more of the following: - -- [ ] Understand the requirements of Microsoft Graph APIs -- [ ] Address issues raised by the schema validation CI pipeline -- [ ] Fix existing issues because grace period is expiring - -# Schema validation - -Currently schema validation is run by [Graph-Studio](Update-schema/Graph-Studio) on the build pipeline when workloads push to their test branches or create a pull request to master. -Workloads should have the prerogative to address all the errors raised by the validation and ensure that their schema is as compliant as possible -to the rules defined before publishing their changes. - -There is however the ability to suppress **noncritical errors** for some time, to allow workloads to -plan and address errors that cannot be immediately resolved. Please see [Tracing-and-suppressions](Update-schema/Graph-Studio/Tracing-and-suppressions). - -Error messages can have the following severity levels: - -| Severity | Description | -| :---------- | :----------------------------------------------------------------------------------------------------- | -| Critical | Must be fixed before publishing. Cannot be suppressed. This Error is likely to break AGS if published. | -| Error | Can be suppressed during publishing. This is a hard error that should be fixed before moving to v1.0. | -| Warning | Can be suppressed during publishing. This is a suggestion so as to conform to our coding style. | -| Information | No need to suppress. It will not block publishing. This is for information purposes only. | - -# OData validation - -Graph Studio performs the full suite of OData validations. Because Microsoft Graph rejects any schema containing OData violations, these violations are treated as `Critical` errors and cannot be suppressed. OData violation error codes are prefixed with `Schema.OData.{EdmErrorCode}`. - -Example OData infraction: - -```log -2020-11-30 21:47:51Z Critical Schema.OData.InvalidName: /Schemas/beta-Prod.csdl: [env=Prod;version=beta] '/ComplexType[testType]/Property[invalidProperty ]' The specified name is not allowed: 'invalidProperty '. -``` - -For a complete list of the OData error codes please see the [OData Validation Documentation](https://docs.microsoft.com/en-us/dotnet/api/microsoft.odata.edm.validation.edmerrorcode). - -# Breaking change analysis - -Breaking change analysis is performed by comparing latest (master) schema with current local schema. Each entry provides the file where the breaking change has occurred, and the element which triggered the error. The errors fall into one of three buckets: - -- `Schema.BreakingChange.CannotAdd` -- `Schema.BreakingChange.CannotChange` -- `Schema.BreakingChange.CannotDelete` - -Making changes to AGS annotations is not considered breaking, except adding `ags:IsHidden="true"` which makes an already public API private. Making changes to existing elements, or removing existing elements is considered breaking. Adding new elements is allowed and not considered breaking, but there are exceptions: - -- Adding `EnumType` members for non-extensible enumerations is considered breaking. -- Adding `Nullable="false"` properties to existing types is considered breaking. -- Adding `Nullable="false"` parameters to existing actions and functions is considered breaking. -- Adding attributes to existing nodes is considered breaking. - - Adding AGS annotations is exempted, except `ags:IsHidden="true"`. - - Adding `OpenType="true"` is exempted. - -# Validation for Private Preview API changes - -Private preview API validation is performed by comparing latest (master) schema with current local schema. Each entry provides the file where the validation has occurred, and the element which triggered the error. The errors fall into one of the following buckets: - -- `PrivatePreview.IsNotHidden` -- `PrivatePreview.Deprecated` -- `PrivatePreview.DeprecationDate` -- `PrivatePreview.RemovalDate` - -The following are the rules that private preview API changes must follow: - -- All elements added must be marked as hidden. -- All elements added must be deprecated. -- Deprecation date must be earlier than the current date. -- Removal date must not be later than 90 days from deprecation date. - -# JSON Description Validation for Public Schema Changes - -Pull requests with public schema changes need to have the below duly filled Json template as part of the pull request description: Please see [Pull-request-json-description](Update-schema/Pull-request-json-description). - -If `IsAPIForPrivatePreview` is set to "Yes", then all changes need to have `ags:IsHidden="true"` and not appear in the final public metadata. -In this case, `ChangelogPullRequestUrl` and `DocumentationPullRequestUrl` can be left blank. -No change will go in without having `IsPrivacyReviewCompleted` set as yes and a valid `PrivacyReviewUrl`. Please see [Privacy-review](Privacy-review). - -Errors from validation of the pull request description fall into one of the following buckets: - -| Code | Severity | Description | -| :---------------------------------------------------- | :--------- | :------------------------------------------------------------------------------------ | -| `Schema.PullRequest.InvalidDescriptionJson` | `Critical` | The pull request description Json is missing or has some formatting issues. | -| `Schema.PullRequest.ValueMissingError` | `Critical` | A required value is missing from the description Json. | -| `Schema.PullRequest.KeyMissingError` | `Critical` | A required key is missing from the description Json. | -| `Schema.PullRequest.PrivacyReviewNotCompleted` | `Critical` | The IsPrivacyReviewCompleted value must be set to True / Yes. | -| `Schema.PullRequest.InvalidPrivacyReviewUrl` | `Critical` | The PrivacyReviewUrl is not a valid privacy review URL. | -| `Schema.PullRequest.InvalidGithubUrl` | `Critical` | The DocumentationPullRequestUrl / ChangelogPullRequestUrl must be a valid Github URL. | -| `Schema.PullRequest.InvalidAPIReviewUrl` | `Critical` | The APIReviewApprovalPullRequestUrl is not a valid API review URL. | -| `Schema.PullRequest.PullRequestFetchError` | `Critical` | There was an error fetching the API review pull request. | -| `Schema.PullRequest.APIReviewPullRequestNotCompleted` | `Critical` | The API review pull request is not complete. | - -# Microsoft Graph ruleset - -Beyond OData and Breaking Change analysis, Graph Studio performs its own set of schema validations. These are typically best practices and naming conventions, but could also find semantic issues with the schemas. Before a schema can be published we must validate that it can be loaded by Microsoft Graph, which has its own set of criteria as to what makes a schema valid across all workloads. Because Microsoft Graph will reject any schemas not conforming to its criteria, any infractions are handled as `Critical` errors and cannot be suppressed. - -| Code | Severity | Description | -| :------------------------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Naming Validation** | | | -| `Schema.Validation.CamelCase` | `Error` | Names must be in lower camel case. | -| `Schema.Validation.NamespaceCamelCase` | `Error` | Namespaces must be in lower camel case. | -| `Schema.Validation.UseEmail` | `Warning` | A property name should use `email` instead of `mail`. | -| `Schema.Validation.SuffixTime` | `Error` | If a property has the type `Edm.Time`, its name must end in `Time` e.g. `startTime`. | -| `Schema.Validation.SuffixDate` | `Error` | If a property has the type `Edm.Date`, its name must end in `Date` (e.g. `birthDate`) or `MonthYear` (e.g. `startMonthYear`). | -| `Schema.Validation.SuffixDateTime` | `Error` | If a property has the type `DateTimeOffset`, its name must end in `DateTime` (e.g. `receivedDateTime`). | -| *`Schema.Validation.Case2LetterAcronyms` | `Error` | 2 letter acronyms should be cased with the same case (e.g. `prDescription`, `availableOnPC`). | -| *`Schema.Validation.Case3PlusLetterAcronyms` | `Error` | 3+ letter acronyms should be cased the same way as regular words (e.g. `adoPipeline`, `advancedRpc`). | -| **Primary key validation** | | | -| `Schema.Validation.EntityKeyMustBeString` | `Error` | Check to verify that the primary id of an entity type is `string`. | -| `Schema.Validation.PrimaryKeyMustBeDefinedAsProperty` | `Error` | Check to verify that the primary key must also be defined as a property. | -| `Schema.Validation.PrimaryKeyMustNotBeComposite` | `Error` | Check to verify that the primary key is composed of a single property and not multiple. | -| `Schema.Validation.AvoidComplexTypeId` | `Error` | A complex type must not have the property `id`. | -| **Property name validation** | | | -| `Schema.Validation.PropertyMustNotBeNamedType` | `Error` | A property name should not be `"type"`. | -| `Schema.Validation.PropertyNamesShouldNotStartWithTypeName` | `Error` | Property names should not start with type name. | -| `Schema.Validation.PropertyNameMustNotEndInPrimitiveType` | `Error` | Property names must not end in primitive types unless the type is temporal. | -| `Schema.Validation.SingularNoun` | `Warning` | Non-collection property names should be singular. | -| `Schema.Validation.PluralNoun` | `Warning` | Collection property names should be plural. | -| `Schema.Validation.EntityTypeNameShouldBeSingular` | `Warning` | Entity type name should be singular. | -| **Enum validation** | | | -| `Schema.Validation.EnumShouldBeEvolvable` | `Warning` | Enums should be evolvable. | -| `Schema.Validation.EnumMemberValuesShouldBeZeroOrPowersOfTwo` | `Warning` | Consider using zero or powers of two for flag enum member values. | -| **Stream validation** | | | -| `Schema.Validation.MediaEntityTypesCannotContainSubstreams` | `Warning` | Streams must not define a property of type `Edm.Stream`. | -| `Schema.Validation.MediaEntityTypesCannotInheritFromABaseType` | `Warning` | Streams cannot inherit from a base type. | -| **Structure validation** | | | -| `Schema.Validation.OperationsMustBeBound` | `Error` | Actions and Functions must have an IsBound='true' attribute and the first parameter must be the binding parameter. This is an AGS limitation. | -| `Schema.Validation.NavigationPropertyBindingMissing` | `Warning` | `NavigationProperty` without `ContainsTarget` must define `NavigationPropertyBinding` in Singleton/EntitySet. | -| `Schema.Validation.OperationsShouldBeAvoided` | `Warning` | Operations with names containing add, create, update, delete or remove should be avoided whenever possible. | -| *`Schema.Validation.ParallelCollections` | `Error` | Do not use parallel collections; use collections of complex types instead. | -| *`Schema.Validation.ProperCollections` | `Warning` | Consider using a proper collection rather than `property1`, `property2`, etc. | -| *`Schema.Validation.EntitySetNavigationProperties` | `Error` | Entity sets should have valid navigation properties. | -| *`Schema.Validation.SingletonNavigationProperties` | `Error` | Singletons should have valid navigation properties. | -| **Cross schema validation** | | | -| `Schema.Validation.EntityWithoutMaster` | `Critical` | Ensure that all entities have the ags:IsMaster="true" or ags:IsShared="true" annotation. | -| `Schema.Validation.TypeOverridesBaseProperty` | `Critical` | Types cannot override their base properties. | -| `Schema.Validation.PropertyAlreadyExists` | `Critical` | Entity type cannot redefine properties already defined by another workload. | -| `Schema.Validation.InconsistentSharedType` | `Critical` | Ensure that shared type definitions are consistent across workloads.
    If multiple workloads define a shared type, they must have the exact same definition of that type. | -| `Schema.Validation.ElementAlreadyExists` | `Critical` | Different elements cannot share the same name.
    If you have an entity called `foo`, you cannot have an action `foo` in the same namespace. | -| `Schema.Validation.NavigationPropertyContainsForeignTarget` | `Error` | Navigation property cannot contain target from a different workload. | - -\*Validation has not yet been automated in Graph Studio. However, workloads should adhere to these specifications so as not to risk having their schemas broken in the future. - -# Microsoft Graph protocol ruleset (Not implemented yet) - -| Name | Severity | -| ----------------------------------------------------------------------------------------------- | -------- | -| **Request patterns** | | -| ✔ DO use `GET …/{collection}` and `GET …/{collection}/{id}` for listing and reading resources. | Error | -| ✔ DO use `POST …/{collection}` for creating resources. | Error | -| ✔ DO use `PATCH …/{collection}/{id}` for updating resources. | Error | -| ✖ AVOID using `PUT …/{collection}/{id}` for updating resources. | Warning | -| ✖ DO NOT use `PATCH` to replaces resources or `PUT` to partially update resources. | Error | -| ✖ AVOID patterns that require multiple round trips to complete a single logical action. | Warning | -| ✔ CONSIDER supporting `return`, and `omit-nulls` preferences. | Warning | -| **Serialization** | | -| ✔ DO use an object as the root of all JSON payloads. | Error | -| ✔ DO use a `value` property in the root object to return a collection. | Error | -| ✔ DO include `@odata.type` annotations when the type is ambiguous. | Warning | -| **Authorization** | | -| ✖ DO NOT use a scope ending with `.Read` to authorize a data modification. | Error | -| **Errors** | | -| ✔ DO return an `error` property with a child `code` property in all error responses. | Error | -| ✔ DO return a `403 Forbidden` error when insufficient scopes are present on the auth token. | Error | -| ✔ CONSIDER returning a `404 Not found` error if a `403` would result in information disclosure. | Warning | -| ✔ DO return a `429 Too many requests` error when the caller has exceeded throttling limits. | Error | - -# Contacts - -| Area | Contact | -| :------ | :------------------------------------------------------------------------------------------------ | -| Support | [StackOverflow](https://stackoverflow.microsoft.com/questions/tagged/1096) tag `[MicrosoftGraph]` | diff --git a/graph/guidelines.md b/graph/SummaryOfGraphDeltas.md similarity index 100% rename from graph/guidelines.md rename to graph/SummaryOfGraphDeltas.md diff --git a/graph/deprecation.md b/graph/deprecation.md new file mode 100644 index 00000000..9c488446 --- /dev/null +++ b/graph/deprecation.md @@ -0,0 +1,47 @@ +### Deprecation Guidelines + +If your API requires the introduction of breaking changes you must add Revisions annotations to the API definition with the following terms: + + + - Date: Date when the element was marked as deprecated. + - Version: Used to organize the ChangeLog. Use the format "YYYY-MM/Category" where "YYYY-MM" is the month the deprecation is announced, and "Category" is the category under which the change is described. + - Kind: Deprecated - + - Description: Human readable description of the change: Used in changelog, documentation etc. + - RemovalDate: Earliest date when the element may be removed. + +The annotation can be applied to a type, an entity set, a singleton,a property, a +navigation property, a function or an action. If a type is marked as deprecated, it +is not necessary to mark the members of that type as deprecated, nor is it necessary +to annotate any usages of that type. + +**Example of property annotation:** + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + + + + + + + + + + + ... + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When the request URL contains a reference to a deprecated model element, the gateway will add a [Deprecation +header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the +date the element was marked as deprecated) and a Sunset header (with the date 2 +years beyond the Deprecation date) to the response. + +**Deprecation header example:** + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Deprecation: Thursday, 30 June 2022 11:59:59 GMT +Sunset: Wed, 30 Mar 2022 23:59:59 GMT +Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2020-06-30_state ; rel="deprecation"; type="text/html"; title="state" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/graph/modeling-variants.md b/graph/modeling-variants.md deleted file mode 100644 index 76ffebee..00000000 --- a/graph/modeling-variants.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: Modeling variants -owner: chrispre ---- - -# Modeling Variants - -Frequently we encounter situations where a certain piece of data in Microsoft Graph comes in different variants. Depending on the situation we call these variants, kinds, types, etc.. Some examples are - -- owners of groups can be either a `user` or a `servicePrincipal`. -- an approver of a request can be a single user, a group, etc.. -- the end of a recurring event can be after a number of repetitions or at a certain date, or never. - -All these variants have different properties representing the information needed in these cases. - -OData and Microsoft Graph offer different ways to model the API and these different variants. We'll describe those here, and list the advantages and disadvantages of each modeling technique. - -In the remainder of the document we are using the term "variant" instead of "kind", "flavor", "type". "type" is defined by OData and we do not want to presume there has to be a type per variant. - -## Approaches - -There are different approaches to design a model in situations with multiple variants of common concept. We are going to compare three common patterns that we see in Microsoft Graph today. - -### Type Hierarchy - -A shallow **type hierarchy**: One abstract base type with a few common properties and one sub-type for each variant. OData adds `@odata.type` properties to the JSON representation when instances of these types are returned so that a client can quickly distinguish them. - -One prominent example is the base type [graph.outlookItem](https://docs.microsoft.com/en-us/graph/api/resources/outlookitem?view=graph-rest-1.0) with subtypes like [message](https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0), [contact](https://docs.microsoft.com/en-us/graph/api/resources/contact?view=graph-rest-1.0), [event](https://docs.microsoft.com/en-us/graph/api/resources/event?view=graph-rest-1.0). - -### Facets - -A single entity type with **facets**: One type in the schema with common properties and one property (of complex type) per variant. The facet properties only have a value when the object represents that variant. - -This can be seen for example in [driveItem](https://docs.microsoft.com/en-us/graph/api/resources/driveitem?view=graph-rest-1.0) where there are four variants (folder, file, image, photo) and one property per variant with the same name. These properties are modeled as a complex types that holds all information for that specific facet/variant. (e.g. just the `element count` property for folder and eight different properties for photo like camera model and settings). - -### Flat - -A **flat** bag of properties: One entity type with all the potential properties plus an additional property to distinguish the variants, often called `type`. The `type` property describes the variant and also defines properties are required/meaningful for the variant given by the `type` property. - -Since the name `type` could be confused with the notion of type in OData, it is often recommended to qualify the property name. E.g. `recurrenceType` instead of `type`. - -A good example for this is the recurrencePattern and recurrenceRange types (both properties on [patternedRecurrence](https://docs.microsoft.com/en-us/graph/api/resources/patternedrecurrence?view=graph-rest-1.0)). -The recurrencePattern has 6 variants expressed as 6 different values of the `type` property (e.g. daily, weekly, ...). -The key here is that for each of these values, some properties are meaningful and others are ignored. (e.g. `daysOfWeek` is relevant when `type` is `weekly` but not when it is `daily`). - -## Pros and Cons - -Below are a few pros and cons to decide which pattern to use. - -- In **[hierarchy](#type-hierarchy)**, the interdependencies of properties, i.e. which properties are relevant for which variants, is fully captured in metadata and client code can potentially leverage that to construct and/or validate requests. -- Introducing new cases in **[hierarchy](#type-hierarchy)** is relatively isolated (which is why it is so familiar to OOP) and is considered backwards compatible (at least syntactically). But see the note about [changing semantics](#semantics) below. -- Introducing new cases/variants in **[facets](#facets)** is straightforward. One needs to be careful since it can introduce situations where previously exactly one of the facets was non-null and now all the old ones are null. For example imagine a new facet "shortcut" is added to the example above where everything was one of folder,file,image,photo. Adding the shortcut facet means that there are now object with all of the previous four are null. - This is not unlike adding new subtypes in the hierarchy pattern or adding a new type value in the flat pattern. -- **[hierarchy](#type-hierarchy)** and **[facets](#facets)** (to a slightly lesser degree) are well suited for strongly typed client programming languages. Whereas **[flat](#flat)** is more familiar to developers of less strongly typed languages. -- **[facets](#facets)** has the potential to model what is typically associated with multiple inheritance (but it is not inheritance so please don’t quote me). Just to illustrate the point and constructing a highly hypothetical scenario, in the OneDrive example, having an item be a folder and a photo is easy to represent. -- **[facets](#facets)** and **[flat](#flat)** lend to syntactically simpler filter query expression. **[hierarchy](#type-hierarchy)** is more explicit but requires the less well known cast segments in the filter query. For example, if one wants to filter on the importance of a mail in a collection of outlookItems, one first needs to "cast" to mailItem to then filter on the importance property: `$filter=microsoft.graph.mailItem/importance eq 'High'`. -- **[flat](#flat)** might resemble a structure that developers are familiar with from on-prem products and their API (e.g. recurrence in Microsoft Graph is modeled after Exchange Server's model). Even though the Graph API can and should abstract from the implementation details this can have benefits in documentation and adoption. -- **[hierarchy](#type-hierarchy)** can become hard to maintain if the base type is quite abstract and the hierarchy is relatively wide. Lets assume a situation where collections are modeled using the base type with many sub-types, but the actual elements of the collection are only ever one or two of the sub-types. When a new subtype gets introduced and the collection(s) quickly contain elements of this new sub-type, client code has to react to these changes. It is important to check if this changes the semantics of the property (actual or assumed). See also [changing semantics](#semantics) below. -- Even though not frequently used in Microsoft Graph, **[hierarchy](#type-hierarchy)** can be refined by annotating the collections with OData `derived type constraints` (see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Validation.V1.md)). This annotation restricts the values to certain sub-trees of an inheritance hierarchy. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return object of a type that is semantically not suitable. - -## Future - -The OData team is looking for feedback what is missing in terms of modeling tools and expressiveness that can help making these design decisions. - -One of the options to explore that helps address some of the cons with overly broad subtype hierarchy is the OData annotation term `MayImplement` (see [Core vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md)), a feature that is not yet implemented in the OData libraries or Microsoft Graph. - -The `MayImplement` annotation is defined as - -> A collection of qualified type names outside of the type hierarchy that instances of this type might be addressable as by using a type-cast segment - -This would allow to keep the type hierarchy narrow but still have some objects cast to a type outside that hierarchy. Please contact us to discuss if this could be helpful for your scenario. - -## Summary - -
    As can be seen in a few of the Pros and Cons, one of the important aspects discussed here, is that the API design goes beyond the syntactical aspects of the API and it is important to plan ahead how the API evolves, lay the foundation, and allow the users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change**. The different modeling patterns as described above, differ in how they express syntax and semantic and how they allow the API to evolve without breaking compatibility. diff --git a/graph/naming-conventions.md b/graph/naming-conventions.md deleted file mode 100644 index 8a04f3cb..00000000 --- a/graph/naming-conventions.md +++ /dev/null @@ -1,119 +0,0 @@ - -### General Guidelines - -::: tip ✔ DO use `lowerCamelCase` for _all_ names. - -- Right: `automaticRepliesStatus`. -- Wrong: `kebab-case` or `snake_case`. - -::: - -::: warning ✖ AVOID redundant words in names. - -- Right: `/places/{id}/`**_type_** and `/phones/{id}/`**_number_** -- Wrong: `/places/{id}/`_**placeType**_ and `/phones/{id}/`**_phoneNumber_** - -::: - -::: warning ✖ AVOID using brand names in type or property names. - -- Right: `chat` -- Wrong: `teamsChat` - -::: - -::: warning ✖ AVOID using acronyms or abbreviations unless they are broadly understood. - -- Right: `url` or `htmlSignature` -- Wrong: `msodsUrl` or `dlp` - -::: - -::: tip ✔ DO use singular nouns for type names. - -- Right: `address` -- Wrong: `addresses` - -::: - -::: tip ✔ DO use plural nouns for collections (for listing a type or collection properties). - -- Right: `addresses` -- Wrong: `address` - -::: - -::: tip ✔ DO pluralize the noun even when followed by an adjective (a "postpositive"). - -- Right: `passersby` or `mothersInLaw` -- Wrong: `notaryPublics` or `motherInLaws` - -::: - -### Casing - -::: tip ✔ DO case two-letter acronyms with the same case. - -- Right: `ioLimit` or `totalIOAmount` -- Wrong: `iOLimit` or `totalIoAmount` - -::: - -::: tip ✔ DO case three+ letter acronyms the same as a normal word. - -- Right: `fidoKey` or `oauthUrl` -- Wrong: `webHTML` - -::: - -::: danger ✖ DO NOT capitalize the word following a prefix or words within a compound word. - -- Right: `subcategory`, `geocoordinate` or `crosswalk` -- Wrong: `metaData`, `semiCircle` or `airPlane` - -::: - -::: tip ✔ DO capitalize within hyphenated and open (spaced) compound words. - -- Right: `fiveYearOld`, `daughterInLaw` or `postOffice` -- Wrong: `paperclip`, `changingroom` or `fullmoon` - -::: - -### Prefixes and Suffixes - -::: tip ✔ DO suffix date and time properties. - -- Right: `dueDate`—an `Edm.Date` -- Right: `createdDateTime`—an `Edm.DateTimeOffset` -- Right: `recurringMeetingTime`—an `Edm.TimeOfDay` -- Wrong: `dueOn` or `startTime`, both an `Edm.DateTimeOffset` - -::: - -::: danger ✖ DO NOT suffix property names with primitive type names unless the type is temporal. - -- Right: `isEnabled` or `amount` -- Wrong: `enabledBool` - -::: - -::: tip ✔ DO prefix property names for properties concerning a different entity. - -- Right: `siteWebUrl` on `driveItem`, or `userId` on `auditActor` -- Wrong: `webUrl` on `contact` when its the `companyWebUrl` - -::: - -### Common property names - -| Approved name | Type | Use | -| ---------------------- | -------------- | --------------------------------------------------------- | -| `displayName` | String | A label that can be displayed or read aloud. Not `name`. | -| `webUrl` | String | The web page for viewing or editing this entity. | -| `url` | String | A URL to a resource. (In Graph often holds the `webUrl`.) | -| `lastModifiedDateTime` | DateTimeOffset | The last time this entity changed. | -| `createdDateTime` | DateTimeOffset | The time this entity was created. | -| `createdBy` | identitySet | The creator of this entity. | -| `createdByUser` | user | The user in `/users` who created this entity. | - diff --git a/graph/~$delling with Facets Pattern.md b/graph/~$delling with Facets Pattern.md new file mode 100644 index 0000000000000000000000000000000000000000..817e118875072ab276dab2a4f0a5422a6d399ded GIT binary patch literal 162 zcmd<{&q+^I2*^*#qz&SxMF@GpEk|@@o?c=Q1#S;cD2(aQiwVOjq5tUFiUZVjXt? literal 0 HcmV?d00001 diff --git a/graph/~WRL0034.tmp b/graph/~WRL0034.tmp new file mode 100644 index 00000000..aba38c33 --- /dev/null +++ b/graph/~WRL0034.tmp @@ -0,0 +1,134 @@ +# Facets Pattern + +Microsoft Graph API Design Pattern + +  + +*A frequent pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with common properties and one facet property (of complex type) per variant.* +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +## Context + +Let’s assume you need to model an API to manage documents, pictures, files of different formats which are organized in different folder hierarchies across multiple local and shared drives. + +where employees can create groups and become owners of the +group by default. At the same time to support business processes some +groups may be created automatically by daemon applications using a +service principal account. In this case the service principle will +become the group owner. People and service principles have some common +and some unique properties such as both have unique identifiers and +credentials, but users will have additional properties such as email and +manager for example. Conversely a service principle won’t have a manager +assigned but may have an associated application identifier and a +description. + +Additional requirement for the API model is to be flexible and +accommodate future needs like a device may become the owner of a group. + +## Problem + +How to model group owners to allow a heterogeneous collection of users +and principles so that API design can reflect common and relevant +properties for each subset of owners and accommodate new owner types in +future? + +A more general problem is how to model a collection of heterogeneous elements that have a set of common properties and behaviors, and some unique properties for each variant. Furthermore, it should be straightforward to add new variants to the API as needed in the future. + +## Solution +-------- + +OData allows us to design collections of entities (entity sets, multi +valued navigation properties) with values of different types using +**type hierarchy**, where there is one abstract base type with a few +common properties and one sub-type for each variant of the entity. In +the current version of Microsoft Graph there are many collections of +items that represent slightly different things, variants of one concept. + +## Issues and Considerations +------------------------- + +When introducing a new subtype, you need to ensure that the new subtype +doesn't change the semantic of the type hierarchy with it's implicit +constraints. + +There are a **few potential risks** for client applications when new +sub-types are introduced: + +- De-serialization code might break because of missing +properties in returned collection items. Even though property X was +mandatory on all subtypes previously returned, the new subtype might not +have this property and the client code needs to deal with that. + +- Client libraries for strongly typed language might ignore some +of the values in the @odata.type property without further configuration +and need to be configured to be able to pick the right (client) type to +deserialize into. + +To minimize impact on clients type hierarchy can be refined by +annotating the collections with OData derived type constraints (see +validation vocabulary). This annotation restricts the values to certain +sub-trees of an inheritance hierarchy. It makes it very explicit that +the collection only contains elements of some of the subtypes and helps +to not return objects of a type that is semantically not suitable. In +addition, you can follow some of the mitigation techniques such as: + +- Avoid overgeneralized base types + +- Think about roll-out sequence + - Consider that Microsoft Graph does not return objects from a workload +that has a type that is not configured in current metadata. To avoid +inconsistencies, follow a two-step process: + - Introduce the entity type to the Graph metadata but don’t +return objects of the type in any of the heterogeneous collections. + - Enable your workload to return objects of the new type as items +of collection. + + +- Allow time for testing + - Inform the clients about the change and allow them to test the +changes in beta. Time is required to implement the code necessary to +deal with the new entity type, both in terms of de-serialization as well +as integrating it into the rest of the application. + +- Communicate the change in semantics + + - It is necessary for the client developers to incorporate the new +semantic into their application/service, even if the change is perceived +to be small. This requires early communication and clear documentation +of what the new type represents and why/how it is considered a subtype +of the original abstract type of the collection. + +## When to Use this Pattern +------------------------ + +The Type hierarchy pattern is well familiar to OOP developers and well +suited for strongly typed client programming languages. + +There are related patterns to consider such as +[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) +and [Flat bag of +properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). + +## Example +------- + +GET +[https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners](https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners)  +returns a collection where each element can be a user or a service +principal, and has an additional property @odata.type to show subtype +for each variant: +``` +{ +    "@odata.context": +"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", +    "value": [ +        { +            "@**odata.type**": "\#**microsoft.graph.user**", +            "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", +            "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" +            // ... +        } +    ] +} +``` From 8b958e667b42262584a0651a8dc87340cb1d1d2b Mon Sep 17 00:00:00 2001 From: tg-msft Date: Tue, 25 Jan 2022 12:52:46 -0800 Subject: [PATCH 253/729] PR Feedback --- azure/ConsiderationsForServiceDesign.md | 4 ++-- azure/Guidelines.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 17d3a03d..f3044d22 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -245,9 +245,9 @@ You should use appropriate [HTTP status codes](https://developer.mozilla.org/doc - A `404` status code tells them the blob doesn't exist and the customer can report the error to their users - A `BlobNotFound` or `ContainerNotFound` error code will tell them why the blob doesn't exist so they can take steps to recreate it -The common error schema in the Guidelines allows nested details and inner errors that have their own error codes, but the top-level error code is the most important. The HTTP status code and the top-level error code are the only part of your error that we consider part of your API contract that follows the same compatibility requirements as the rest of your API. Importantly, this means you **can not change the HTTP status code or top-level error code for an API in GA'ed service version**. You can only return new status codes and error codes in future API versions if customers make use of new features that trigger new classes of errors. Battle tested error handling is some of the hardest code to get right and we can't break that for customers when they upgrade to the latest version. The rest of the properties in your error like `message`, `details`, etc., are not considered part of your API contract and can change to improve the diagnosability of your service. +The [common error schema in the Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors) allows nested details and inner errors that have their own error codes, but the top-level error code is the most important. The HTTP status code and the top-level error code are the only part of your error that we consider part of your API contract that follows the same compatibility requirements as the rest of your API. Importantly, this means you **changing the HTTP status code or top-level error code for an API is a breaking change**. You can only return new status codes and error codes in future API versions if customers make use of new features that trigger new classes of errors. Battle tested error handling is some of the hardest code to get right and we can't break that for customers when they upgrade to the latest version. The rest of the properties in your error like `message`, `details`, etc., are not considered part of your API contract and can change to improve the diagnosability of your service. -You should also return the top-level error code as the `x-ms-error-code` header so client libraries have the ability to automatically retry requests when possible without having to parse a JSON payload. We recommend unique error codes like `ContainerBeingDeleted` for every distinct recoverable error that can occur, but suggest reusing common error codes like `InvalidHeaderValue` for usage errors where a descriptive error message is more important for resolving the problem. The Storage [Common](https://docs.microsoft.com/rest/api/storageservices/common-rest-api-error-codes) and [Blob](https://docs.microsoft.com/rest/api/storageservices/blob-service-error-codes) error codes are a good starting point if you're looking for examples. You can [define an enum in your spec](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/storage/data-plane/Microsoft.BlobStorage/preview/2021-04-10/blob.json#L10419) with `"modelAsString": true` that lists all of the top-level error codes to make it [easier for your customers to handle specific error codes](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/storage/Azure.Storage.Blobs#troubleshooting). +You should also return the top-level error code as the `x-ms-error-code` response header so client libraries have the ability to automatically retry requests when possible without having to parse a JSON payload. We recommend unique error codes like `ContainerBeingDeleted` for every distinct recoverable error that can occur, but suggest reusing common error codes like `InvalidHeaderValue` for usage errors where a descriptive error message is more important for resolving the problem. The Storage [Common](https://docs.microsoft.com/rest/api/storageservices/common-rest-api-error-codes) and [Blob](https://docs.microsoft.com/rest/api/storageservices/blob-service-error-codes) error codes are a good starting point if you're looking for examples. You can [define an enum in your spec](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/storage/data-plane/Microsoft.BlobStorage/preview/2021-04-10/blob.json#L10419) with `"modelAsString": true` that lists all of the top-level error codes to make it [easier for your customers to handle specific error codes](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/storage/Azure.Storage.Blobs#troubleshooting). You should not document specific error status codes in your OpenAPI/Swagger spec. The `"default"` response is the only thing AutoRest considers an error response unless you provide other annotations. Every unique status code turns into a separate code path in your client libraries so we do not encourage this practice. The only reason to document specific error status codes is if they return a different error response than the default, but that is also heavily discouraged. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 9ee29877..f0b29e18 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -310,7 +310,7 @@ There are 2 kinds of errors: Property | Type | Required | Description -------- | ---- | :------: | ----------- -`error` | ErrorDetail | ✔ | The top-level error object whose `code` matches `x-ms-error-code` +`error` | ErrorDetail | ✔ | The top-level error object whose `code` matches the `x-ms-error-code` response header **ErrorDetail** : Object From 45885bf41d0f84a5dc28e6a320ce50e69b998f85 Mon Sep 17 00:00:00 2001 From: Darrel Date: Tue, 25 Jan 2022 16:06:19 -0500 Subject: [PATCH 254/729] Update graph/patterns/dictionary/index.md Co-authored-by: Vincent Biret --- graph/patterns/dictionary/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/dictionary/index.md b/graph/patterns/dictionary/index.md index f84cbccd..066bfdd6 100644 --- a/graph/patterns/dictionary/index.md +++ b/graph/patterns/dictionary/index.md @@ -24,7 +24,7 @@ Dictionaries, sometimes called maps, are a collection of name-value pairs. They As dictionary entries are removed via setting the value to null, this means that dictionaries can only support values that are non-nullable. OpenQuestions: - - Can/should PUT be supported on the dictionary property and/or the entry value + - Can/should PUT be supported on the dictionary property and/or the entry value - What does OData say about being able to POST to a structured property? Will OData Web API allow that? - Must an implementer support PATCH at both the dictionary level and the entry level? From 648caf444d94fb6bceaad110299c49be8e97b8ec Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Wed, 26 Jan 2022 10:03:06 -0500 Subject: [PATCH 255/729] Added navigation properties --- graph/patterns/navigation-property/index.md | 41 +++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 graph/patterns/navigation-property/index.md diff --git a/graph/patterns/navigation-property/index.md b/graph/patterns/navigation-property/index.md new file mode 100644 index 00000000..50ce9c39 --- /dev/null +++ b/graph/patterns/navigation-property/index.md @@ -0,0 +1,41 @@ +# Navigation Property + +Microsoft Graph API Design Pattern + +*A navigation property is used to identify a relationship between two resources.* + +## Problem +-------- + +Resources often contain information that identifies other related resources. Often that information is contained in a returned representation as an id value. In order for a client to access the related resource it must request the primary resource, read the id value of the related resource and then construct a URL to the related resource using the Id value. This requires at least two round trips and requires the client know how to construct the URL to the related resource. + +## Solution +-------- + +Navigation properties are an OData convention that allows an API designer to create a special property in a model that references an related entity. In the HTTP API this property name translates to a path segment that can be appended to the URL of the primary resource in order to access a representation of the related resource. This prevents the client from needing to know how to construct the URL to the related resource and the client does not need to retrieve the primary resource if it is only interested in the related resource. It is the responsibility of the API implementation to determine the ID of the related resource and return the representation of the related entity. + +Additionally, using the OData Expand query parameter, related entities can be transcluded into the primary entity so both can be retrieved in a single round trip. + +## Issues and Considerations +------------------------- + +Cross workload expands don't work. +  + +## When to Use this Pattern +------------------------ + +*Describe when and why the solution is applicable and when it may not.* + +  + +## Example +------- + +*Provide a short example from real life* + +  + +  + +  From 53efb2c34c4871d3b081d322cdbf8813a78dfde1 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 28 Jan 2022 15:45:51 -0500 Subject: [PATCH 256/729] Renamed dictionary pattern files and removed folder --- .../client-guidance.md => dictionary-client-guidance.md} | 2 ++ graph/patterns/{dictionary/index.md => dictionary.md} | 0 2 files changed, 2 insertions(+) rename graph/patterns/{dictionary/client-guidance.md => dictionary-client-guidance.md} (96%) rename graph/patterns/{dictionary/index.md => dictionary.md} (100%) diff --git a/graph/patterns/dictionary/client-guidance.md b/graph/patterns/dictionary-client-guidance.md similarity index 96% rename from graph/patterns/dictionary/client-guidance.md rename to graph/patterns/dictionary-client-guidance.md index fd7b2c20..c3196765 100644 --- a/graph/patterns/dictionary/client-guidance.md +++ b/graph/patterns/dictionary-client-guidance.md @@ -1,5 +1,7 @@ # Dictionary types +Note: this document is to be moved into a central client guidance document in the future. + The client guidance is a collection of additional information provided to SDK implementers and client applications. This information is meant to help understand how various guidelines and concept translate in their world and clarify a few unknowns. You should always read the corresponding guideline first to get a context understanding. [Read the guideline](./index.md). diff --git a/graph/patterns/dictionary/index.md b/graph/patterns/dictionary.md similarity index 100% rename from graph/patterns/dictionary/index.md rename to graph/patterns/dictionary.md From d6ef25edeba55429f9a3ac63b2ffbea8522970b2 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 28 Jan 2022 16:35:00 -0500 Subject: [PATCH 257/729] Grammar fix --- graph/patterns/dictionary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 066bfdd6..8d9b14e1 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -40,7 +40,7 @@ Before using a dictionary type in your API definition make sure your scenario fi - The data values MUST be related to one another semantically as a collection. - The value types MUST be a primitive type or is a **ComplexType**. Mixed primitive types are not allowed. -- The client MUST define the keys of this type. As opposed to the service defining it in advance. +- The client MUST define the keys of this type. As opposed to the service defining them in advance. ### Alternatives to consider From 5d37b3d2e144f5233fa51b1b344e1d7938a06928 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 28 Jan 2022 17:31:06 -0500 Subject: [PATCH 258/729] Moving patterns to folder --- graph/DomainModel.png | Bin 150674 -> 0 bytes graph/Functions-and-actions.md | 87 ----- graph/Modelling with Flat Bag Pattern.md | 131 ------- graph/WebHooks-architecture.svg | 215 ----------- graph/WebHooks-vs.png | Bin 64391 -> 0 bytes graph/adding-subtypes.md | 107 ------ graph/deltas.md | 189 --------- graph/entity-complex.md | 58 --- graph/evolvable-enums.md | 104 ----- graph/long-running-operations.md | 328 ---------------- graph/omitting-properties.md | 103 ----- .../Modelling with Facets Pattern.md | 0 .../Modelling with Subtypes Pattern.md | 0 .../PatternDescriptionTemplate.md | 1 + graph/shared-types.md | 210 ---------- graph/type-namespaces.md | 185 --------- graph/webhooks.md | 362 ------------------ graph/~$delling with Facets Pattern.md | Bin 162 -> 0 bytes graph/~$delling with Subtypes Pattern.md | Bin 162 -> 0 bytes graph/~WRL0034.tmp | 134 ------- 20 files changed, 1 insertion(+), 2213 deletions(-) delete mode 100644 graph/DomainModel.png delete mode 100644 graph/Functions-and-actions.md delete mode 100644 graph/Modelling with Flat Bag Pattern.md delete mode 100644 graph/WebHooks-architecture.svg delete mode 100644 graph/WebHooks-vs.png delete mode 100644 graph/adding-subtypes.md delete mode 100644 graph/deltas.md delete mode 100644 graph/entity-complex.md delete mode 100644 graph/evolvable-enums.md delete mode 100644 graph/long-running-operations.md delete mode 100644 graph/omitting-properties.md rename graph/{ => patterns}/Modelling with Facets Pattern.md (100%) rename graph/{ => patterns}/Modelling with Subtypes Pattern.md (100%) rename graph/{ => patterns}/PatternDescriptionTemplate.md (99%) delete mode 100644 graph/shared-types.md delete mode 100644 graph/type-namespaces.md delete mode 100644 graph/webhooks.md delete mode 100644 graph/~$delling with Facets Pattern.md delete mode 100644 graph/~$delling with Subtypes Pattern.md delete mode 100644 graph/~WRL0034.tmp diff --git a/graph/DomainModel.png b/graph/DomainModel.png deleted file mode 100644 index 869206bfc8cbd2ae739b84ba605c22de25fc77a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150674 zcmY(qbyyqGw>6BkxI>FuDPG*&S}0a1TAboqyjalUPH_!V+}(q_wODY6;1(=M@^SBb zfA{^~=b6l8^3Tl4*=L`%*IGML{fh!F76ld(5)!WB=Z_jlNN6u#QB5&WUM`l>{Jk$1 zWH${3S)}SIs>7F?SJpDBGDt{u@z_t_U%%XAI)B!4Lqfvq|E~}7uv4i85)z_F@uQ5E zm+?v0>rQQj)N|mS)w+1b9h~90czC(kaoN~&+Fi6t(YfQU0J-2}L^uX=uvo8v@JGz| z1GpH~m}maat!w5hP37>4@+k=_PNLt=#Ge;hbfcQ=!PQ3EzQ9LVrHSr5D2#q2G(VXL zuW$mH))nItLx8nGV%s;_dxCM5irZM8tkC)in&B&Hud&ZZQOL>IiL5d=nin)tIL9m= zez7lSy(<+PcR;FUiOxk`sxx=~FhKY5`-H{BI}mP@cii|nCd9ACbijJx83p$Tb1W~= zd53|X7U(6d6)vb*uF*$ncrden+GGUjV&o^S? z*A(Yp+TOmGmy011V4crj`n#_-|E#QlljtQvflifeHkR|dSEOYRxY#jmez6)oGW6LL zKt-BcsL)nz?PmKMU$-F!|9=NUiuqpydAF}Xz+^pf55kT!$A12WjrEDaY)ZLT>1w*Z zAkghAdsX>pS+GD^vrX=>0V@ewnXt2m+|Y%%^5!F;8hsaodY3QkE|x8fi#hUhm!b}K z%cW^{>vlh42XQfUachV{|Irz1I4#COWh;+_$EV;pG?7FNn{#tA(a(CqLxS@spqcRg zW8NF_y(ZE|8Xh8m^ZiJK3eSfbkord`me8A6j?nl_GU6W^U9tqPWd8eTTb4n_Xu}94 z`!>)YBmKnLXLZSyrXPZ#VP@7I(l`{Vrm8wd1<~ldAgWSq`jm7^QrG1k7(Bqh;7xcj zUMB5YG+mO5c*pq5Y>aY8#2HB^S){d*-nE(^>-2qRnK)rR!F}j#8mb8I>K4N|{**lp z_!7jgWS|iurJR2}N?OnIES>BApLda|HdaTH<$RThmRO4sW>jYyRfBM)ny&vaEkw@8>it6|C9nrnXAd}ho8N56 z#Kg;31c%vHhPcTy5b!zt$=lTE6MFYfl1!MU&?L;ovZ56<_5CA7TIs+Qn+@}OS6)1) zpM`dAGms3y99N2+`_I8_`6U=X=|$j{?qZcWI=7ML43y*9f5wAXLqKOqs&uL(^T^SwnuT@ z9Q)#9nO``=Kg^zz_r5h_+cKqvF3A1LCr*1Nya;DIx{jzJZ!aiox9wTm`Wa?9X0;e+ zojdDlTjckO-4Wqkr3h1N+zyR{v{&QqOH;237uyQ*^Y=gnaDLqlO3hOLPguw#RFIGa zCPdTk80WQ~VBcT#&qE;+4bJd_L2RHAw-Y?r8=KOxj{9snE5P4?AF=caJ2lrbfsgix zRmW9v;m>jbhw(+<>JoPTcPM1apx$CM_%-%hPQ%s!gv@a0H)CQn`YwXz0X*UDXshZ2 z_KQ&zbwWu3Gj)DdWu_!qxh)vCPC~983KOEu42Nh*P!bwSIL>SF%hHE@iz~L2SbmgW z)meP{mwD~>=Edx%^KG3!ig4?unZkF2G6`yLosD#L1vd5`BS@#;{bz0rWAl(g04TiI zp_{rB&s+8upvybN{Y3U*Qi&9=<^JSsi4>-w<8q*KGrW(6i$~Z&W4~c#*EW7K9rut} zvjjg|-14H4+&m1A(re8FEc?Mpzr4C4bp0YWF<ZkWj=lS1g>7|a6evtJwtT7T12_%%{M(M1&(6mGB$3`S7zvBRBu%lLZbMo zS(Cfw<4!H+ALE$S?C2)*k*@v5n{@#d4<}I^aM6pNFX4Q5w&tyzC$ZLkv#BcDvGl1U zS<;{87|SyaM3!1GDt~*j)Sy}N#GOyLM91>d7R<+TOUa>!P z;?e~5ZT3;`6}(v|3OW!1uA>CCBr`{qo!l2OjQ0n)0!iR&9L5rL;@y?@%p(lfMngK9 z?Z4|<$b5ly+R0t$LyH}K-r$l7zX#;+=Y(j7=m~O*Z1Ui*w{Z#^K~%urCR*V$w$kBH zB^F_O!&@}yMxjO5@Oqdirf)wat;Xurk2^{F3RZ(-Xt`HYa-XUcfoA|MmCOBGs4kSM zJxC}F)cZ{?ZN0_kedg4_;Jr)au$$^Z7TH^%=jAM2c;mrAJD%-bR(lt0|Jb8xeoLK> zth@?a=k-xVut!SJgI%qLR=n-lKA;%)HF|2i!x>&7~Hod z?LkL?v9K^d2Yr(Eb8I|w&#MLrYB7|T$D*nA?FsZAE%!t|^w^d*SA*EpHOVo+(*Ax( zKPH{9aBcX#N8FB%ji)#B=LxFMK*SOF z(xS3`-@?%Y$p?#pID~UP^f=GgqeQMZ^Wk9`X}S!hyZYAM$j`F&>{T-6JP>aoT(+rX zlSp{5Lz5w_w3tGkLKHhK1U1DY=;)g&M{-S7Syb;u|6!))KWWF>GZHN-oCSsQFJN@k zt@fkkW#V4bbg5uEuD6vUm?^J*rEteI<+ndP5T}&HGj9KjBxc5=;Kx{Y%1ZTml)B-< z_)jG6Mr~JcAi4ZuaC=2C<}F_MZ$PgOW?v{HJ|SmT*TBWqV;ynvvY_O?mz9nbBrlq- zrh&0714#olT?>seSh)yc2^ys;+1tpxo&({NFwBh6=WWtV@ogoAeG+{geq&a{5zo5B zwdC@Tb>r4|UCxW|q3@(cN~vRb@xyS3dVf|eR2dK<@_xiGoS-{GUBDCvdUDjh>5jO_ zZrQ!LxPl`B;Uv^xpP8Qz2K%P7G(C&z+{4J$l@R=paq4G9F!jz7D&6c&m}US{&9Glz zU2fhj+3eXltUrQpwFOOVif-JG>dMhK&ryo;e1zJ6#7)H-qjA9fQBHBvC)?Zn$X>E_ zFLgaUzRK@8R@W4XdU{{(y}U{%CQu)VEiRYJu1$5q4}Z1W`tHlV*xWw=v)Z$`IR*61 zz7WX-JCE>t#r^#Z;2q2ED-MQD5yJgFLw4B{mO_#JKjh=wT%%7oZd<>}8j@o6GO}E+ z>CA2Ai6+!|q!hbexh2IErxROO28r;Dgp}Wh&h*?}@rL^_-MDXjrQEK|YQKw+N9p+a zBmCCnf!*bBq%Or|+X-CEfzwd6z%6!`#Ur0IFu}|GK_2trcd$PXt!rM1EtxJ_NM~$X@uCsoNu3YJ>RY{DBx{$SnE1^;6CBJV1oZa2)lL= zcF-#vGr@>etgM@{3?4~M634-)H_E9Wd~TN*u}L`%>vxw1qjL>fP1hTgNxAeo39=zT zgLe1Pf=}3QWziO2ehlvR+WK9j{8k!1GdL~V?saL_>OA_JQ5k520q+Zirb%{~ZWXj) zzx@zQRZ7L1AH^6Ak?62EEPzdO88@2#3m#O?Z)coFQBLDD;tm*fo0dR_ZOZKzd{X`> z@v=2ut^WQr-D{L5zDn`~BSi+4Zm-!L9vnK|iRrPB~KfY85r<`>W_g1^% zPN1A5K`bDQuK6pHYa5=tL0zP#xP&x>hUFXePi8g$ zSB6x2-7xvFLT3;SWq=nV&wblgspNcZHon%j8i8RhC$TVnm3y&7x8geyigjoskJcAj z{rW~t=hT-2+s=QY@sU_Cq)q{o`ty>hQn)i}$>l=A^oS`?0YG5f+%g;6{y}DIMvAArn##Q^`rJ{xTSEkINhjNJ*@k-F9K8oZk)?FI?uC z(HF+?tkZ1LmqWRVOjQ3ZP)#8;OD{$1GJEp{v_1va?^7w7(g?06|ERoAJU=mMt$BG{ zWsoE3;V`~`qv9~g@!&3!XwD8nZn8Jne|T!Jmpu!?0efhVi3@1g2x{3KvI%J0L;B1Z8i)RzP zK4827!5_xLY5gx2>lm2hfzrtTNqz}6NG5v77zF~6=Z}CpJz$_Wp2;A;-^2BBO}ER- z=Djl)3y5K{Vvsv;RV385~g3)rOMc(*@$)Hjon*bH z(dFC~e|&x_v*G=nYANHR0POgA`V`@vnS#kH@o^4`M;1hQd(&Do1mHlCzh)u3nEJ- z7NO_sC9?y}WIL9Q5_Ds`l>q}Rmy~O|uRkyd?r@%Y;5%8HIP4CnlOl0usR(|XKB|&2a6)+^( z9{L?mthlL?{^URMPJojR!pDkk0~57)Jv6ioj64>Z;Q65cls4cb-;zt>F3aROz0NVG18&fEm5%?3hu*5Ga+3GwePSMrq2KVgDvcI}YdGFxq!H{io+a$Vx;fsK-UFa7ua`_opW z0S}fTd0_`(k_h>QWRD-zI8Lkew(RF&PO>GY&i4r#eF~p(_KIdL-ou#2Vs6?>&a2nN z$i7xNNX+(wG|t(Ob+J422_`f{ZIKTzH0@TW(N|SC3a2L5#+bfPKJ`T_3s%BwH;7TK zRC2$RcG>(p0UMj-Vq}+!c0o=(H+;5ITIRzJE*X4NR9MJLtJe`O@6=oF2M4*ju6eIN zkAD)E%EHp+HG#T*FMqAasRaG57K#>PB*2D?mqFoLFEF4~{QKWjj+ic3$8ot4c^O{~ z*3d;yDx<>!$-C4u59zvGZs_i~g=A>CFI+3;OJ6_ZuUl2$Z7q93ICGm`AdU8DmDgSo z<1{DE-0RuIzqdK|Mrtc<_R%T$o$w)p=(GtbJwNf9_tfl!)zubp9;VX^0kD0c?Yhkg zy`-TyDh#PYT@!zo-afsH=(ssuPLs=31Emig8r^PY(TNyB*&J11AvZYW~fIeCO@a82lNXO2oCYxcCb^j`Q*U ze~2lJU_c@u)EY@~em~|G8PNtp--5q{v-M=AOj((gTo}=~6_MnPA$!d}(Z5aG*czco zma>FQoI_kf3C}qj4A#WT#8RPp+QTx2pKPx|zt>8NLn?gPq|#&f4i;#o2Z^+BnG>_ix{v|=7d zjo!0iLK|MHN*@Dv+Co9t16}~pO*U2}*j&D$!@Drm<(BS$CDBH{6byXi)MG4Qz54Pw-Lid6j<~}?EiU#OD)NdG1z@sUv*&&S3<|lHgHv7I+#@kcpopWFJV-TVzG4@zzO>1F);EbdDerY-! zj~apSzls(z@q_syZmSX*6w;blr8>=>vVJlNBT%*A%jKcNBMAZC!nl;~eucNKUht;``VO8ptCWp0c{5?x*AdM*C za8=|~zv*o0u{iM;8sK88qk&1HYkQE91Mw8zd@34CG3RtL@?GlwwgqS+j(9kBa#(3T z)kH;iTyH(hN2ihWE8ic8)Oh!)b#;OH3PEGO{<2OWQ#sQt zWuiYf=eE{j4k%kHVVjedpnpB!f2A(Wq#mx{3qEG#(mn8$zt(Sgkgv^!1)Rvwix(E5KaOY4Z=8~}Ft&DFqi>!=V(mOJA0!UtFjwGL@yn>yu%FjOj zbQ1Hp&P$b&jY&>X+xwj|fxTqam5O~1^O9h&9jmxDFoS*E;CI&d@S>VXZo6TkZ*}i3Padkmy2A+NkawS*9 zkbZ}>pSAxjccpsr95ZWYc~%@@+lNLd7*hK^1Q#8;#Ldl?Y8Gj!XMT@E7Z9VidBK|I zgw}X69qRv4aGknmZlLY3HXRh1pKvm1qksR zpx^b7XdeJ|LPd{D7drPIyR}>34HrND8!73Xsm8l46lwq!u#u!>)Xn}XnEa0?Cf3?r zIKPmLI$N>YY2$J_=x=dVR*Q^+_{KW`DFyfj=N&(X7qevl-;v7@Ei7rzHIFocKovgS za|r53Oi{a$d2>Uw83w@D-#*QUGQ|p|gmu}Q3Y1_uT+ci2%~Aj7{mESDIy+ahEzH_x zwp`mjH-mfYkgyP+O8Dk%g-i7SanY(c8wV7>f|u(;;@g~hcSptW&{hK(h9=`j zO7Dz)WByT%&V3DyRj=kh`um_ZL?S0yyZD}@Tvb>_ogGOw>GYig(@-3NkD!{eyM zwjKf0d)AMy|Fdp_4e0?Xujw!rk=iEEg8?9&v7LNG40(gqR5m6#EvTzt@Z5hi0=9vG zm`rB!t#PM(=FelZ#SkNJ@KX@DVvT^+x4qZe)N3D>>i@%XXu(8N(mcM}S+bi0@ay!K zf_@8s3&tnEr<W%}=d> ze&8zFV=s0S|7Se;x80ZUu7NE0`s4G5=lOU^8j16aOKj4(+k^4Au0H2tQTKASVXPbL z+63Sq6akGhXHQ<}g<(1P#EhS{>XyfGu#s8b>-qKK)c+s^K~A4k;! z?q?SLxgi4)-{h%D@U5~d--SBy}0CL6Bt$tbtHugR=KwQ(ytt?b||JeGoF0`@U?lG=X zyp`K=2H(PmpU%IjX|!>XdyJ!ozYJQbEWjGLveNtmdM@=)XD`+thTCym-QoYVL(6yc ze1v$ux&uG|Shc;C#1TV!)zdRZC@DA{l8HCGl(oetT6mdfL)PD+KVB(9O zEwf;w7C}8Y6C1>#vs+vu&}`e*Ofb4uccIKHc<3K|K~JT~Am&CIYZd&394s)nwo%%Y3fh0w35@#9*Hk(4m)mwG+2Fi$uO;A~;k+#O-OsrRmnNRn z(yB^v6!WY7F;aav?C$X6YI9?Yq?gB`TXZUyL7nN;licGi?{==RYb|{)o$LV)+EGC- z(vv)u+>K-Z%+gag-*NBjt4K*$tg0;HPL84VXHi$q|!iG2K6ENyDd}izj^C2)K}o@Lfr3 z`DIVrdYAZy3)GJIJEW+|gt&)DJ=vTq##6v{>D4NN7VmC5&cC&p1+`XOZXm8QU3L_n zFXICMyFuv3Y-%qY)D{?V8J&cV2j6Di2CZ-022JyPuY(=|Q1r?gxK=AkK`k7OLLJv_LfRQI4=f_tG%5-piSc8TK*D^GsV- zEkDK8dB;6Ao~xKTM5H70D@{DG+DW(){Y}>}clbbNf-Nuq;Le?{5Y`H(15+Ng_S1Ph z_f`uT?#2+X-v8q8OgP1N2C#^aSgZADzeq;FeUDti!27Dub^fSmNq276Gu(lpfj)b- zF|kkYR$3gEPl(a_`!G5Q1l{rQ_O5A{6#h-}MR4?sT%eGI6DkRkYrx*rcAR%AJD^mF zwQ-yH7hz8hMvUVIMxhWO_|vD-%-WQYG)!um=4YFYC!f&vkF1!cD??IZx}d%FBGVJ! zYE5PI6=6|3w%&u1pH2g5r+L!|tD@QxW1T9iCA1U(LRqmHR$~I1xkPtvJjFx#$SRQp zMYS6V$p!b}J&gpjeP40|UVos#bU&Oas(-lNuL77QQw0_DF+Q@)YOIy;cc&{wi6G(R zTt~}++a1}53>iFr`y9U1h(4Lc0^D2y78}LnnoLvaun9P72RUQL?sq=jW7g_K0ZdHZ zx4{!Od3M7K_^jHPv$+PH7AK3BFC-+HnEyoa&AIo_)s}ZLe+&ie_E0BOd88cok0c~y zC&+jh63+WazAeSKI4yRE*ZdnVKBD3q%SLOWA1NKp&Nd%%Wa#_0&hvW$3^2Lz^V}N) zqvHae1}^*O=jSi%*+Bmbqv&*5P5h7#a0mi@#G|q#-Ih=EW;dO0*trZJvrJ@fafRUd zzTkzH>v9|Kep>kbib*Wl^+6}O+THdB=2dmuKEu)S>w|$!CmPI@SJPk&r znZSlNo}a~>K+8vA$0i1#D~J?)X(kjKzTIui=d+vMjmUCh>c2b>IQG?Ng3dsr1E3Wq zovqiQ2-pUkjSyG5Thzm$JK(O`qe53kf*{CuU~dKKO*@=C$evQp1gXL;1rn80UL-)Ui{@5tAV=C-G)1x++Xu zbqWb95VMW>;P}QgS7$h;HLaq>&0`{RNx#eKqXeUjtKI^Exqj}y4@FGh{pHx~STigB zQ1R`sHvt^ebnJz+GL!_s+Y>l9bB3ZP-*Ag5?iycdo2x`78XipG&~cnNM@Lmf1cO>x z>nJqpob~kdRE=)-Cn=--64w%^+S=pY|CoH>3hFn@csEF~=`gP2nAfIK7HV~{q^^3S z=tYvaY(AmFV|MSKOPH!W7-Lvem|GkLsfaSy(%ZMy9Q~$`g(jGUy%+PIn7B2hSB^i~ zLBbt3rte^{4gQ*8bFWXG13{5V;RYqNvTj}2DT}d_=d{^7Zbd42O(kFfDde{K``)0e zC7Di~*C_S~4aoRPY8M%mR?Pq8?@J-^5r684>wjHF+QYggNZY;QuVC*)Z|)I^(^9BX zcNt600JFPPeBCZfvJB0VcuxIBI+E&KG0(FXmqtAGdedpY?PmujOy^rIqHYm2M|p zNm_4sDC5phJOH+rMz_BT@_SC_QR{*)yV}53ocy>gkFJZRIif;-RY-c3AfxCPci+#< zyhtA14@Xg^_x#C+%SGEGLC^NOesrQQ6cGR(dh%$ETAh;b)SEw1rElzN2B4UuMu zR+CxM|4T>Es0BnEJawGJQC!8aaAm~SIb`n5S~V`~s#z9kR?(GP^&l97KZ>NMq7+|iJmm?^kZyX#3;85tA^53CO=h3NLU^#!mGL*a%#Yu zRjJd2*y;;TxBRQAmM!kG`=UIxzQXw*pTPf-%03TO zh&w|8ASxvr>FRNY8EdQQ3<=-jg0hK8ZWG19ed6O# z7zvuhv4)*SDg^5Kq1@)tOD=;FGdHZSouWCG-<;w;giPdyq)-G7+x6n;Q(4g-HD zLoOJtWbcB^l2wQ_2(`qTUCjQ83<>#%q2b)I0yB3TzW+NA+siRfkXsR~GQjWOet1jD zG=M`#Mw)g-E)5`Nl%*O@G~er>Nv?CG#4FlGeytS#^?^6kEMeUbB0=l-9)yy*Z?b&V zM1Z(TTl7|_Mu?ib3MRaDNeQd~i^!)~&wKkM1UPVJF)pZatf$m(+W(a!F=$CDknknkwXL@3 zm0!{FLj0nYqqv6hTIKHzyE5Ohav!^)rZ{3mtyA%}%Ier~BY@+wUQc4blvOIl6qcQ& zrZ2BC8nOyl1#A4|L@Q$e=qmC*5Hc7*$p1ZL`sGp!q}Y+k7v%h$n*|xGUDsBmV{<>2QX>T)U>J_074x!6Ml1sn%_P~K3>5f{(_kj|A zL)jt?J^y%{en^__7ZhjCH3S*f>yL**M}0V6C%Vb8c`XZ3OaM}HB~<4cQ_JTXe5s3u zJ`c}_*|8g_RZ)@JTa+I2l`>Oe+p|jc_x-?tYzxRU^^pGAwXZaM#WqKAL?w`K-Pbl? z%S<|65|;%_$Se=<(GE{#!Q|7eU_!Mkf6ErsIybVgsN^T=6D(rOf|g=ItgQo@nwUS% zu(ML&t4s2c!!R=!FhW&j;W~G}=mB{gONO9%o2{eq$0FQ7tu<#U}J7K-*?5=+NMfAv~NilaiXxfDNqG*xcqX6W&(cw63RVvMi zT)3U}3}S+$klgz359tQExHWMLn3;kIo9BGgqIm>9x!J1iQN-HMLE{J5F|}jK+iK0i zzBPxjq!hnt%Kk8^RUd{VP@kH(ISWWGtzZ^sI4u8K5TWMB3g93UEPo`H3EmNIGyDSfIN+iRM*TBuTNcqPT~0fUn%$S1^Vce&u9eeNN%o>gB@z20J(QwMMeytIR^O( z>>mCkiYPW^moPeXEib5v=u`RAO^o%$$C0s0Xrr$MnY{g@gJ%p!Y9QjS)r#fjz}knj z;iKWr!W`&WFc}IhkMjM^{kw-!6|L2>at` zrSvs58T?-ZzWqnKeX-qww)p~uLxvmQ;aI-7OCKgC^q~8bs0x!>msJ{C5%@drfu>f= zlL~ZZ$-R6Hbim<@kIFD53BKRC6jt)fjbWb(Hu!;;gI@>U zsT%Mb^l~8{5WhE1bxyuV`My_LF|pvqCSl11YV^A7%l;jnRH0krf4gK241mKXvxF4h zC0dTBRvC4?#XCMaausp$X5b#d#l_uR^@A8a;++V_pi$CzR;PB0py>)3dO0igC8eq< zLQ~MZ(l&cV4kIQKov_6%LD9HeRGtHXw`PCtZ>4<_Sb4lqaRpS|FmqaGh`@$xC5d10`*$S z#3B7B*N)7O=;|Q-M$W=f>J1mBeW%gyLZ2h#cKhP4#e_Om5f)-B7n;#4 zVg#!&XKh27=t^}cda|irEfSPniIA$3xjLjcU61RYl`3wOhH?L%wo%tK^H3X^CtlS} z5+9pyD;Qu{1Q2aI{~nL}ODenc{@+#{vfTh3O;}cxSIRjl!_LoEhI@5-glket!b5VB zs`G2)sLN06fLMIpda~ocWjkkG9ShY6f?($|`-{(=gd!h+pX>fOo8n-j5CFA4-s>}K zTIYDL)bQPMG7yvdwOc4#T}+>h0h%x0eDB-kE}08cV$gH6>Z#$~ev*xz#;y>Wng7x`Gci{-I%`+HpfV@tEnLKW zuPxucpfXQBs$ZHxEBLDSAH~tNyTl)T3M{STLnUU-EG(Mln~z40mU^FHiRR%gr;BW~ zZCuSKm*UghF@~I;kEbo3#|u@Jl@2c{>hIpC=(jg#YaMQosbX8%y1RAXn$FHnHK3%r z*9OS%#evivVt8fsP8PZRo7ZY#s;+4e_Ew#4m zokJ7e9qP!&lvXU&i_Jk(&@w1twBL%`l*ZLBC&DS;D`ZAn9nBsajDLBLB2r_3-9^&l z-FZ!U<*(auW_gLaU4Ai>Pv#Y>>hnUf6aVQSMgjV^fp>zZ*fT&md zyFcfC1t1I~eVkdzxeR05_&|3!j{5Fo1-7+(^JDV8s<-8s3rv6k zuW7^)y4~>66{jx%ILx1`>*4P>JFNlyD*Mg2byvOFEj#;u=hfJ?uFix}9pHAHeadOv zB!i(!5gzKe@VZj5E||ka^3BYf8cFC;#jLTv%fc}`WGbtp)o(CX0q@6Ro1Z~p%qKu! z_ZsIbx~oO==XJk3N3x=uV{rFHANG<%^UEx~RHLi4-{s_(a0~YdyLD@fUHM5qxeOY- z>ZwI4FPDYq`2C3eD4VQoh0=B(=bHd!Ot*<_eD_4~g&&0e$8lxB!$ZJ+p)v@y4F0(X zKfE7oi+xQ>+t7Cpx-SFVEh`w zECo|kRmF`Vi(z|O`17FIN}BJ?^$+QuF)DjVUVVtRH|;wlT#e9Tz}(^Z))D`yO6&1bBGIrLh@kP9 zLoz{;w=MaS^YavDbCcOb@FeF1BVv|BJ-EV<4iQa0FLspSkU}h}F~zibJe3+3DtJ&! z)SFF=RGZ8HgdCPe@f42r$j5(xlt|`yfC4?b0|nYTO?Tus`0Xsx5*ga#sAv7=E~~!Ifb=E^7ryA3uaYzOcpL8m!(3^I1=wth6{885v~>I==nz4JrJ^ zLl^?La@8XZR_VYiNCd={=N$ zgL?=`;Qd}Fn{XgO`V~fZfu_CehUJOT*!mv_PBL?B7K6`A0NaqhEk1}q-+rLLD2Yz% zEmfAEanen;eXdk|tzPovI|np#D2u^n+x%MlZMk^YWnq;;9bld`oT*SkM{mTmwGrTY&r%L)>T|`1;CVBb-*6OTN^RxfI3oM zi!sKpo;Qb&4`V`XYB`cVyFFz);c>Lfo>vF?JHvP~`i+{yc)wEi`5UNh8|9eesCIu| z_l%l9s?msfJKP5QCLwkSK zR`}kXiAk=v|9F<~%L4;27a_UGb6%uaGM!W5(LBR0`c18=bs}MsL$2~m@cuY~`8sB2 z)Zxs7^w}PlSaOJf0?@dz;Y&smm#OBoebG%ek9oQElzZi9Zhw{qIkALjVe`ZDOYm#y zcAVaC$#H)O>xIDYwcHF*jghB+sK}t5bG~VDKAXRJFS9poz)d^W zgWc%5r!>;?p@QYrg^-eICX>aw_ni^58g1dkwPM_Fv?15zEG4~MKapakLKQ*|HiR8j)Kq;#o;vP>bUs}vJAq*P9xvJa zX*W@5!zY{NI*_#qV_-cVeqsd-TZ)Dy zVw!nE>SAI^9(jP%2L}z6>G%TKU@u2V1JeyjtdJ@HrNUR*F%{Rt*~2gn4g(_-?Kaz$ zg~`AEE2;La_nR2u%K8a!hf7#OKM((;_6pie2-wLc0DDBget*TTi@)J}zVA_C#4(!P znpWdduMMK%he3J@R<8)*s+@IQLh0g9S~{K0kpiqb{4=|iM6M}F=~Z(bJ|BR1;lWL( zD=ja%=R8-!H?QU|7t({7o{l6id}?UN<-m$F?WN1@ugt+s5GpzzwdngtJVtyPaj&O) zmy4oTI=Z^(0=voLIHWk4wgzz!SqT6dfq9idv)SA@V?`u6^u`BH$S=SmiAw890+->m z`NMUhl9e@kzi{qNmc;bdc`hLfmtZ;ow)aL3?KT`QSA6slgF1xdf4gsv_Uu`ZU|gLS zeYhNO{vWHY-}}*VXeHebE=s~;DMZZf|5m(+(&&_u?&URj}(S z;^n)`sz+pZ@!F8f#c0??Wj3T=((i1!%a_^Qic{~D@;f|8kYP9d3Dr4{iJ$I!?pt=z zw81C=As&!IE7sf44s7C$U*KR6%>*-hS(avsik=?1dp4H~kyFH%8nQ+b@CTm zrsNL~)>JGin15D3GW}7ncN})Tpk}w+XOM5E(C5^D)6)3rk11m;goEpJr|8@0p0!?* z*(a#>;d?_PeU)z^0Ux9^v8w|1Qc`}Qi@QASV*X!8G|cY8BO96DV{8I!5@0m(q{rXJ1{@07oj_|wsUdQoM2 z;v#1>1gF@6+gy&W$epZ+L9zn3El?&0(!`WT>KLJb_O*}so%g!>SlG}GZE{+gltAf! zLI34O+xcpvES;+5z>R97@Kg>bhnHRX#onNsUtw{1JLt$u?w)_Pz+ZCHo=}YTzs>Na+{MmG{tX6_D#^9!HVhmG z_Vndp5kwX`?~*DTRQ9Z;9!DFdrCMzM6$q_+neUCIICb4DRMdbT_OqNox6AhII+b3Z zE*{3fbi`AfCR5gL-g|+6vLdzNS=m?E!j>&mfRB~yB}fn%A5#*LAkb(_GXcwo!j;dn3d`(o{P z-W2Kmi>>}}T*9&mLWC?xm;w#98BfN8QvF+Uno87RMDrJYs2tK2`b!qFn=3VW9_IRf z;dW@Jfcv#7x29+2zMS3QWz`!B9I?1&P&zeKyjK#PQPIhM&q$My4o5}X*|Gk0VO$Bg znG8}B_j%godGYFumq7xM|1ZV#7G#W)@Pza9(w+*L0;0zy)`YTLAMw)U+0m=v`{)M- zbmczMa~R;}oN5_2du|NdFF0);p_zjqhN5%RYLApUwy1U`hEiEY^-6bs`(n?ErcQ;7G|M-mGr@qe^zPK+O zoO5=pz1CcF&W+6zQ#T&Vqo#9y7R~cB99v+)9H2n%C#@%=#`lUaGdZg3J*@w1Jj{Jv zZ0{|?+sA9S3O%}=^L@ki8(()?WQrk)H~lF7Wgg>xdNE>w+wNyifb>OdTAYc>PScC8 zXRWVt7TQIb6H?$x{YF+`#3q}_qcN}|Y+ZAm1D;DPTAVnYs1E2+D)yP?0YSb;5EEf) z0G=iL^c`f!v)Idc+(=cRzf{7);$>kI3k3_Kf^&kM{=py9Azgxghvq3|+OG>hD+ zM@ACUog1EF7$9=D;V`f6bM1)KGmRJ&x7?1WbJ@Ls!Ld%{qKcWlY40hVs7*#Kl9Z<3vKQ_ zczc1H-BxGD=VhDN?XO}S(_i@v3hE_tQ*vE0h9#=o&gA zdNgN^Q7%ARoC~wy{a3ud4}`*qhc=({*3`iU%fi&|B8ZtF5}fDN>p!(HLnTyh=5K}-9+m--cy_NzbZiH~2fgtuVzt1RLo`9WR9VgJ^c*zhW@ZvEq ziwT5FzJdQ9{~gp%^%6=v)YLT3>avREW@q6msBD+ojM6+YU0UEddttjNHUnt> z8RlS=;)+2oA{l`lGOekad`DF!1tQw6!;C>ogHjP{YHDj!WYfn8)JJcH_PaoEe=nua zt|cwfLttP4g)snMNn!+*svtEu$XISup6L#k;AQDG;sTd|6Q~n68oIG6HQ8;{1j5-8 zE&AwqS@5q{!_5L;Nz3fyO#b=`!wjhgO_BJ5&e;+>eTJ9rWM&Q7D+SC66_z$QYTJ zDyqIL)|q1qnXI*af5^f)6ru)|vGYTh(t~he>;7-JUQLt#YesECpfFZHZAZrliIy0A z1Wlp%f}(r^i=kAKgah04zeR%Fexp!`JeBZ4@3qNyvMTMrPyGdItpW9ZJ!BQ>ecj1Q z58B5RG#bhZ*Wgf7?Xm>fFEx`b1hl}!O(Zf%vw~eO*%?4x@zpgjm1dA%D2a0nW$zSM z&|oS4a$#r}S;Jy`=C9&R+{Ja=)r0cLCKMZ+-~(j!tjP9TCk8MxF9+ls{_6{PWqF+CrDUAHKm8rV|41S6nhXlSUejl2nU z{9^Z|??;e8bc5AAk&7HG1wV8@=j|^+OGGiFv0EG%3WtAE{0r_iAt(%(U@E-Oz!Iyh zpO3USgZ=fp3t?7jN1ThW>nvEtEO}GALwaksZJdD=3(Qj4+93w5Ah#AvkZN~xzFAA9 zVx}l3&#ZF}?a8Y_IBr((1C|K%YHuzfiW<+)k^)3xiG73|RHdIYDQRDk!kQd*a)Rhg z;4s>7(99v&RuP`4&zsa*1i8xmN;woDT#IJN_~{(*NdtspQC>O@P%Jkf8?>O1mpzy@ zWN;*33%w?mVSC(m&zNknR4*1A0|+s;@~(*7+UfQABI3n2*K4|8uoO!$M1MD&S@AIg zf5i*=?t4&X;5-a_@aTVFB+wRa{@r8Ql?#F~b)Sc&u*AA9P6aqLVTqYyA)m+1+u5+8 z#sFpGy`4q@i0c*MqKejx0M3#>5U_4Q8wQ(>A7x>3T(VR^tr(9obRhD7W;fw=nk_|x zbpsR!R`^`A6^Bf>GJCtD!+u$`PTm;+yRDDDkHzr?|D3(eq5&DWp;O=lvO?)=qWFb@ zdWm3j@2Uk;S~<7L#6nSRr~JZs%0*oy#_;W9P-*PE#6eUA5FTd2MU9y=mN#cQZ>A$iV-8|zHn5a4E`fND6%#eu8BO!Phk#TaBXDpL8C<%> zc{UYZ-T4p>g@GjYuSMj()(r$+cy^iR+rUx19(7b1B7ol!&gTuJAU>C?3XHR~cH0ktHMWC8}H=4%^ed%)z zVsdO~I3^EA7i_La-df)-lGxb=5c~c-{;vcOLfX7WCWdi}&rL*G)~p8l_)N$|{~A9g zE+h4|Zh;f^k(#Lkp>noj9@cnxJ|NX2?_a-j!h@w?cQZi{C3V6LZHHVngSOgy z!$;SEP9&hzr5=P}zL_WZ`9_=*!84qbBfMrkYOYwx++MPa6c!vA0B49K7A71hC3+W$ z3%92phm9nLFVW5ey8UMsf*~zjn1P|`Y8ZhZ&W5p=CSJWDaqjhc!%z@+`vS|1v{?>G z48qpZ{03NP@8WoWddgtmzcLR&B&KZwZ}@^Rd87!;{Kt1DOMY>MqZykujD|ad_lC)U ziOvJ{qMp$B3;y%43-=)4jBg5}A8|K`oZ)fMf2q%T_FeQKU1)NgEjRy$SLI+gUhEz{ zL+W5RpKl)BRHggXv5Ql$$!79CoOmH3={2_J{fJFi zMM7I_d>!Glws5G|)SM^Hui+&*Q++&B)BYZ)7`If@yNAik~DhJpb z=T#Nb@OLN_5|+@T!$GDjdLxqJkqNO(N+2qqhA1;JH?mg`x>7pgAOHq1%OFH}_qL?& z!)M>7YQwK&K1kwUPqE}k2!o(q#6+MkT-tTyri=+H{T_(@c$7!u3sy81{Xd?58xHq& zD;*8@+F~Fl35ZmN&zVKoERm|M=RhP~zw4nV-p8b8!(kNqI#)#C4A0Ipd`(HJ^dugN z8Yj)OJoU}C)M2r;(H6nH+ObQA-R4ds1g#~;*}D=EpOl54A`JQ`Nkp`)=ZgDj^=EQt zFg^Jzo6bq7I5M|k`ZR;9ZfZgKWl#sdA`(e=cQzI*wuwxF&9z7Mudkd@UZp@!rLNUx zvC01Njwc>8RZ4~jL&1q(ViRg{Sc*mQ!73Av2}Nl$T}87r%hGeVj(ABzgx24Pn-+#5 z4{Q#Vs*A2@(Ed2`f26iDY|R+&e^!S}qbn+mB}Xbv@1+oe`9S5ndRpadl=C=yLLt6l zlbxb6(MTCUBoLza{BnuC&zMj&gaBUikwt*m)2~91{ zde~7UL=Pr3%6A`99cv5oR%C6LdTP*2ZT#Xwsqvbdn5Qaib$lC(kM*k9V6_hQaoC<_ zpn-2-R-L9ZbZpbgxaQ-`ARO<*t(TAN3eZ989nXJXO=kLLcv!0^uu|uFA-5!(&-w9e z09zBn)G)*)@>hhVFv6R4a+m4-)_yuDEeRNNeBv-iGIe0o+Ac-pB<6AvXChaxyS8A> zP?`o*N8xz!VM;q#lroBhpAu4H7g|CK!b5YWCROUlI7Sp+eJ*Xh4lOftqZz{}#T=m;!sr8SwRltaJ*_SEg@JfQhxcx(06bvA{MqG-tR%O$+ z&PsFsLBM_6fWUqz6De+k@o3Us0n=#~S0dBkhI$3y=f8k?H~QuP{USp2xXZcWdR-am zwULw2{R8B@R#ih%Z-0mu`n|X;$=)=`KK|l+iGgGO=Eao9PyQ3HzVIuP9iPpj&zTIi z3Ajjt>T7Dl?tO&6y@qn^BvDuB(?m#79QK!5Y+pT|m!q!)QTA65dQIn1hi14KG2K1q zz_v-H1(M+Qturou_FTR^msuoi1p1a1QlE&--lWruuSOzmDt|!s{;^vDn>oEUBZV4B z?ciyFgvHt8&=f~d7A{rO);76cJ~*P+B<{HFxWz#QU$XbrYBE*LXowcVy(12Najg)4 z_U1smPR$!_s}ObaBBL1fU#u7xvl+*@;^BH-73v97e`d z8Xvhm+O_1fxHvQ&MDk0RVgYcU)M?s>(^pO&4#Js&K>9W1)l0zalhCF6&Q^n)RMZqe z=}APKL!C}I*jLxxN&bwiQ!7C2zA!v}fU1c>pK%xY87ZTzr%8tm3ib#xH+$!U+PpNW zK7RHVVO_7$y2bJ)&R&oAe(Ew(@hSL>oDNmwN7P7`uz^R}8HI*@Z; z$Vrnz-Y`(Rn_3a{b}xPpKRDc z#o*xJ!C^=yk7LBi;A;Q2bXiv9&50B=v(Hmt-r<{$~1C&aCb!W7BXK$(Ya1YB7j4prt zkzb&?0AP}GR1&0nD6H(`TAh-J#Ie-7Oi>H<>pry8a{AL(-PY`z;e6$Wed!D{h~}ic zEHlQRv_y7n2qmL9OsaI6u4-jYj3M!4eG^y^PclQ@5`V*{a34ldrc^(1lsBJilrPuL zzTLjnLvDsMV%!L#F7CY5l^uaaa3B1pNy6Xy5^}Q|j^r)%I#@Y!-t3Gagx9JFCotQ4 z=TnR89qpJ+d&cPplZ$JkFmHJ67@e8 z=f60y7(vm(QnWW!W73~}3L5e&Ms^Rqoq9-fd|IBdS9a9l_1;Xy)N4|hZ}YxS9pU_H zmyNnV!&2W#gg)x1_jTC%BXpDZWES*;(Nd!;=Ivrr_c}^V^sKAlVw3sIPgG7Gd(2r0 zR6UEf=Y{Uor}Z@M>2GM{3%d_f?(5qd#HQ+Ahf%)%;JxkwO@Z`@L?{&?)o5)M@Tc6~ z{6*@uzs7=>g1E^cKW>IPg1D4#CB5beBMN3ej!y)b655)d@iN7Xl!7Ek4x`tm*zik{?W2Acy>a=QbTSv$#$n#YMjKKzSPkxgxiM`+|L1lYG9Q<*$i&g%(;KOPY1O8ad0|NJoOnnM(yV4z zm20hTyMiS1>RF0z`Sk8bNKaf{^|tSgUz_(N+#XzSs{R9?3@CVR_KMl5STVE7X=WU$b{j?bVpWmQ0kT ztV|Vi-vSoPxhsreAYRoFj9+NAqVPg)H!KI@pO5yVOdmu!ZmSo4M7&Z^hj~7cER~Jvd-+0JAthn}lzgh7yJY2lNlRf>0E{ErG(z3hr?c{)aZGg`I?CG@8 z!CkULhOBw_WHB%M;WQ05yXCjqQG5G$^46C)-}~R7-5_5_-`lRmlVjiA3*6D;~)qXLv}!iLShX_NekZ{=g9#7ArYS3XUjh^_=U}Jq6fXd{)x=DVbs9$`C_)CysB!s z-Ou-&*QF&8DH?4G1@f@?f)wl1u-X$Ze2>7#CiQfG9YV{^Ly$$QwI|C-h=Q;rjiO`> z2^B`*4c8LBpTiDle||$y6atT~rK#D44Bb!mx4cKcNWbTroQ-UjbFzuTpuq~$2x^C2 zvFyA7M*ZRydC6UK8lo5Kbj9C)x>EgURcS@;#LwG(LaO)2^FFJhRE?zhw`neP~Y3aGY@ZG2* zsE?=ZGT?dNNQqq3d6WFbuWQNq>(!p~%U+bQ!wHZ7FOq51zPL}fPp6OvfKwI7#!QB+ zChAx!K8v7AXGiEBhZXcVbG&#gTJzAuU6CtKc8<6TEem;(@BSJcejCe|jRwKtiJHh0j4}6f1Rd_wB{4h1T~ ze$mDBLNX!_$mV~C;Tw7Z`+vpj@H*wo7q8=3?as7H92_)5-{b39Ev_$Bkl01rFD{$U z#2mXRDNHLqyCZ4Z%zCvZKmP1W`^8$*2iCpc=J0*Kjo&jN`-e8*o zRkD34;xuVkX4~b@=#Izz_<+0m)1xP=-Ma1QZ;iH>x9ah$clDh3Y-gAwPbl`>+fdBmOldC_-&8RTL9Eh8 zVguMmil(lFP}ORser>}d+^PhAT|I9p9slMzYD(t;oA{T#5DEkD!& zs12g;-g#d24?sV}lL&Ynjpb3pqml3d;><2+8FUgMkF&M)=uCQFDSdaV)0L+#m8~v# za(^IaP|g)u=_gDL-ba66N z?6b7RWv-~mZ)A>e3{@aRVL9^nfuYS^(R!)dd`cXI5dxQYk2|q?A`8!eE1Y0wll^?F zLCvvUlfz55-B6SL`%X0Dr8}=aC964LuK;oH;qh|Rzri(PEPyq>)7e6bm~`~g%>bX@ z?%{X*%$L=E%@R^_N1#{wpf#S4^~*Y-p760RF{B=qh?Wdnbg9*Q=(oMz?~}V*AYS`E z|G)=)FTlcphw@(-V#R0M+rE5=2s8p33VWjQx(V=$pT&*f>$u3+ynjeN}tJc%eP{V=Kd z)Yys~GC`@4UcjxzmM(^LVk0~iaK9SzLlz!m#zp*lE}ep?el^s34pFdK%(R&e1)M ziCN=(9dUL#i7$oWM3Uy{!UecJ0UOP4_t)}i`$ILtU9&i|4|p^25#JHxm81%i^F=YB z{c~7CG3K0$Ag^AmbMHYT>vsef*3~#~-UIY5-_ezL|EtYY`?*_6oHbJXp;gGs1q&Ga zyiLKJZ7IEnhNmCf<1J1RIJco23=IdlU61(DGbDS?T74)Gcj+BoG}n|5-MH<-??gmI zM8SR#7yVB+SI4eEe{#w1=CkLjYhQ}RSd)n73Q{c0 z%K$ij88W1}w(1+ODtLM_xmzuG+V)TlP{wxS1RX*+l&=D|07~Dg-{^6pd&(hiC1#%I zIovJb$*wmBmgFl(_3%qYJvOY|kN4Fz`^s(NbE<{N;sA@7Ux!RuZ7APESS8z7T89&tU$Xjr z3Jal9BsI|c??qXDENtajUwr>`e{`qyFUsHu8CcKhFZLw+Fg|0u;B!QaPNg7=Bkp-T70g(mYpv4ZSFCyNtAZKWM?akQ& zj*)!xH-#_s?{z>{j4?4JSsU`Kf`64|2relu#^6Xquq>*tPw#ZFRFoRa-l6z23mZ*m zWwLECGt0UyLoqKKO;;tHQ7k6QNh#$H?QP$}b*>}qof-M+Mp8)W$WrAeR1P}(3U+0A zm5`EQjZbRZoncyolNCBA>6TLVoC8E?mrynnswNt-w?0_I17X562ez0-$SpR)+&yZb zgQ>E~@u!DTyg-UJpugL_`#ohM-afOq%(89(I^y?$QnTXStRRO8!;)gq< zQc?lP)mfscOdQ$E-ljp9O5sZqbcw>}Y=t7vQ_-61g7_OUp@vOUzWU3vaY-3~KIrCk zn%DCXCw^WKVQX8F=h;?4)yYcr@N$>fD83!GIVxwJ1R(EHzu%dQp#SjknuNVL<5X#D zY9QP1XJ^<6c&4?bP+|;m4rGD>- zOgdf!&agJNQ&fr6TOOrpMmlo0s?Y)r%%X`}>Tn`6IhHSSi0bu0j9_}PD|+qV3(Dri z&()MZ1Sc+-;K2(c+x{O?-hH4hk`3> z_~~bijuO4T@k#H>LBG-A(JUTeN0gmG>7)St=-KFD5zr>8EBn#VaQ+Y6^BNyJu)(ge z0;bnNP#<*Bm{1TVoH8UmqO&=KCrN1@RV4JDb1`Zu<6&7aH=Zl*~TpY z+4%?wwQ@F6WE1{LlLv_@1(y!87sQcKcxX%?3MY+T<=KQXf(40#lYxrv`tgl!dWD3I zY`>&<0VM*v1weiu#SHA-Juah$rPzy$+3v)&6-Yn4sIu&g5Cfo{Qy&_*h+bDnP5otu z*d;>)#sYHcz!r{lNGia8v03j9dmOjQFxDwA7sTUAmasLb-9e!8-a~W4CL!}crXUZj z5D~4*Hevic`ewf|+*nLE3k+kAH=mY7TV?NZ`-%T6S($A=D-Qh2AXt}1eZFm?XQ;cS z5!ZDMH$|!J3fl4ueeT;4cILka%19uG4H}@^0Jbe8i;)<@c4gPuWB_dU8i`XPRd@GL zygWRe%-AL}fXxtf9Fop}nGvs+QX!O06y8w!JU5?EAhpCi8=j8WluWVIUrXJJsX3i) z946Ih&5F*@=%SUFR<+HMFkntAN=_k{Zmn8_JPN`(KzH>HzZFYCIioO=eU(7D;mUVK zLI6V$PD`5_%{}tit)Mg>IMuXl$8*YO$Xe35K9(m5_~Y60m6VsW;MGSd;fBIF3B{j! z!-&C0i*yP?XecvZEo$KeirlV2JAHr(Vs@2QT_}uxDB|c#`H9b2wn@sS5;$SMJ5=EW=AKJcznJJlW9ce^CGG?m zfHqWL2z2!`TfAx-);BAYzWjV~KcjH=_*;>m$tzcvt}6?2oJ9fNjy^33 zRg5BiLo59g0@41~x(E6}Sr6ex16BVFj#MZ}DoVzyzbe{XDms-p`|3*S%{vYkDf!uE z-MPWpd2iV}8)iX#zAr8FmwHJimMG-{Ib8-HX0ATc(himcmr$A=s*QW^{)#29HiZ7p zMrnwxN@C8M2Vo-V$JzfBc{4%DCSVjUuAYZ%QaABwURaHAzTNLZ5?vUE--&)E_ z3`WL+%*3QKKvO=La~Q6BS0&0bE8FwUm#N?7^?bWIo=4xl4JB`P|5bt=xHg3=YrR2CHK6+`lsHNFhiibM{y>Y;VGcWY7UMWW5yO!`Fs`g!V);K<@ z!z#~}=h=YgG#*lcm9dNXDm{0b#a7oto@3o6``L1X2fQl5z<^J{5XE=J78;n`{RUc$ z)76_d?GFWFHGm$-@-|y!4E`P$X!pZrlv_h2_Y^aEgs;XV$3|pdZFpyAJwT&@;$Gii zv~DEEO8uSP1A~x(%L=+{qA3b)n^W~VpP=C^R>ara-6C|4vS-WZG*MIDp0pKuz>aOz z>Jqw)320(pZ4P{=Xv0LXl~c>PU1r*x>~3Y`zjYfFFS3BR?95;+brcN|0q-5awt~bu zghtghaVG#q{hxPn8f>y3ssn`E2k6O|+F8!kI=!y{R2qNY1-}*^<9r*1f;ANZY<1E5 zHl|MFv3WsdpyRq4CQE;6aOTq9uXlZ$?K$lB5+JU|rpDtO9bIVg4Bpa?ZR<5v8+^Z) z+zZR=VTTbpHWBZ*FlN<{rq+~kcAcYjAqb#YX;=r^T{^C45YD?o0eGoFEC%f(6h ze)w2z{H}le`{9iJS!BSuVr~bNS2mqyHbew{|Ni034Mc zS9aFDhwYY6KewC-JkRsrWr)ccD5UN0sqYVK2Yk0WowD&%%@h$YIptk*iPSP=c#?Vv zpvloJOqo4pb~CTgx*Jb1Ge|CGX&fj zO{GSsF=&n|*tKJQIzWEuXSd9E*F#rs7B!$@yzMr|++0IMNVnBciES zv|SL>LvLh-cmQW(gNSFmT`*8$0lB6eJ43P9I@N`WoWDK^79&=nP`xVS#x~3vFVh9y zKdswf3fcNU>BxQyktq4abb3L!5REuelEa2}Aa3zXGLl#X;HCI}S%3i~z8O}1j*B1o zs{M4aiKPRz!{b=D3@lz;UY_uJ5bugqj7&~OPV|RlOC^eswGqn2%zqzpfe0ul!G+Sd zNX#^7?K=J`w;zvU_S6qD_Y*Jz1<39)5XC@lAI^lcB4wK4%rc5kAAnx^uH{7$SdGn0`J8=PH?AQ?ecJ z;`;t|UqeC(Tps&`Pzo=~ksWxi=Sv!>_nVZk@q{M{{PS{+@v`vE5-cpFy$t(Rwe`Wg z-NGeF3)gTR^CTB)PoQxS4y1XES|y|T41L=Kqf;~QqqOlxb{gS3mj6B#^5?0I2^FwH z+zJ}9Ax0VE#G!XS7z!2=i@la>Zvk9W=JzpBur&WZ$9+P9V2&3BgSPIoWttuG56e%# z0c^##muFvY#d^Du%Pcbv2ZD@EAgw8$+_&O7;6uy07E)HlF_^Vq3}3h?H+ksA%|z~s z{0GnnG87|JR!tyvLzr&9xS-KheM)Q>64C};5Tv0Og0`@uBR@N|mqTF?Xa~^G`foLm zu6nvFo(oxCAD}nc&BMf5A2?0ap@;w?@z)WFwagWJhsEF>45SiofQkAynoH5I9j1&I z7)d5V4YH5(xy|m#Mme#MU=okUiy{YYueF4FUZbr=z<@V@h5h29>AG$UmDq}WbNCH6-T?)Yu1%N3%PR< zq22&mu)VQ7cXxNum%C9wPxvf&(ro(h`&b2WOCvcYByCYS2baDemlQ&E**8H|SXWlao4+x9jraYVNWoFSaQ_%K)?tBv{J$q9ys+n)g zG`iglfPNg(rwi)6wPZPzq6uZyql90$Xk3_;Q6Q<(P3&k6nb|t0i!l7OXTa7I5$0gy z{I&|vCvNovlwsY@z%7>+J3OQs6?(VLSl-}GuWE~P;lTG+UeYn6ppTN2m^W?1{Wkv@ zON;Fo5DfT5+5*&~O90s&dctbsx)Z+rN%P)AM2^sUyxe?xz!ZRIdPlylaJcQQ0j(|@ zD_JD_h}-rY1=_B+C6gDNI(55hHw-qYYdC(Co?Ou@&fY<^!EeE(XDz;zp!2FPWyb;n z=PZ03@4MPE9Zxj1w3>Wx9O#_^&8Hh6tOo8iQ7={qvix#*56JE)SE<`=h}1wES~}pL z>o*er&V;IUt1;#J3Bd>xYT*9$%x5Vf?!9tVZDb_Ey&-NZ=(M2F`ySrVUvxGM)Bv2i z*y1--rd|S(^EjIOePGKZ=*b#zH4fDD!Wjc*X4C z%vfnyaeXjYAdf58C_nWV)wX%p{8m%8jbY>jE3C>l`4XTvbFGQY-etc|`@;hLXmU3c zSBa+!%kZC#qyX?(&nPgylAZuRBvS0$3qUx(8$wHDgjjx?f2w$cEW%Y#OCuG%i`_UD z1|Xp)%PpqBE6442=Z!J;r?O-M8RK0)bv&Swdi?CUvhEF~IH{22n7~|w3PKAT>ncH3 z72ls{BDA8{wjpq=O|ZLFW0N55<0iNi!f$ z9$3g|1IalzI|ChV!i^1@+4lcLsy84g{WSO$HBkQ4=x(_^_f~}jL^Vmm^)2MxPPuc9 znoPzLOe>)B=tDcvyII%GBgJ4Ji9j&@kk4$?4gmmk7TpE{KBqNc*rkl}>Z^CX6AoG( zGAtNVf-4Hyq=-cW0YlmWoMa;FBRR$~Y9}C^0pdvmO-2oVL6JL=dJ#@Q5TKnnbMnLnw>E< zDQ;y`f<_vDv|8^m5;5k#>Pivj@10HE0Y!^;r;!FkbU?cl4;b-Jm8x>A-aIMLnl3gm ze;Tnc_*$cjWTkbySqC~!(h9_TMVyTo#YY*3gq=~i18Y4>#ydSTgY|}6GNQ+=Ts-@_ zt=n@oPw0ybC}JeElH?z8fXiGhtD(EyM3qD9waik;@tf`cRM5htIldg^6CPZxZ60Md{44Gp7#W))rUX)oU|%+Y5^y} zrr@3k^dW^=_sRW+E8$vIH`Uv|D zajTlu9B_huz-0nD)9XMCZ9D%-45;WC??d;>mo1&dM>IwjD`3rXbQ^2(&Hl%lSOZv4u zjXd66S`Md+BTOboCRSK#{T%5l&4#*k>Do9MGOE7wQPVBXL(?_X((>3^%Z|03QTx-( zX|7zu&g2j&L42I3ijzzHCL&3wY!}Dr`}+u(V$s{RkWt|qk}XME4fA(e0-sa$cKhWi z=+cJfo2tsoapb%Vv>73MFc&p*?>|9DjBO7N%JB^Ymj+V%BKd`)N$_?>aQqm0{rZ1T zu$G@dQpt}-DTO2kTulYIPGXr>XH=*XE7=a{vS?QEPSKy+ROIO@YxqT3ysGH>fTlx5 z6v*Rwyzq4P?8lV$y?dKH&l0irXF<%U4WRV;d;xyeAON5)hOOX2ka{}8qU?UByO<%;49Jk50??yBM zsGnx0r^Vwmdd-S8f-^MZ5=Z{I@96)lnA_noOr>nZTsAZ3>n%rfM9DW?Ce;XB314cG z=jagXgz!Hy4H9hU@SHBST(-x19zS@++Y4-L?n*43E65g(LGx#8>r80ED0MYv@0OJ_ zqN|rBU*(wzd|vtP>j~V6d)l&_JzgNw0mutCs0>jCUnRY5)?zLExE;Mu2le9vPF*|3 znuiP5mS52?sCFD5pQ}ApzygVevOnVES`QN#&C%+TOH(WQ9|F^#B|eL>fp{Hp2!(Mb z*?=y7d(-jqJr4J?@6tq(;sYM(Ad`Sr@>izQsrJgYe0fX0w*1OUzf3^Y_qSd;Er@<%$94SuFk6Wu5e@DO>LCuQZEV)jW zD8F3hyg&dI@bk4BWkILYz#T`O1b=|s(g_Dh)4=0b6`pRkQpvW+{MZwjun$a#_}S(; zUm;*6q_Rf!6#O_ERSEOoc-IVq`Pq9x&zEY0uvPl$c1F=LhwtLNOPKt0@%v`yvq8k~ zrRgu~bKmE#->w(zxa22Y1^tAA zTx+qWg&5+e36_IQtNxtoOBZ=nFo+eU=K4mo{ z&@wl7XHE?)Ut_~Sn3~c6-$6tYWkY+`-2@Eu^q3l7l_EJHxd)@;IQkJLx_2M1v5x7^ zi(3VJG5lT#2P3l8-!2Irr+yaZcD~*gZvy}CM?ErGG>ajDy`2}+CLWnmO$4K}GF>rq zYYLt8gNI*x7^ONJXtSn6eH_@DD3S*($1b*#l$Fxi)`3(kfnf+}c}lC=ExPGTPIFXS zX7PD%{gHdxe_eKYx}JS))ZW(A^jmQCP65?N1I8@Le5uJDC;bh_;D&gR}c{@C`A~EW)@=?SkD7F8| zq@fZ}x{F{y?OEsGGV^yBTSE}SBzgC>4^X;A))^WKC_QY%qK9bvC9wB~gp|i}{Ix>8rgST68{rtC$kPN8S;OdVmzp`1*AYm-WQusHjof2lk26ad-k( z?JwS60D(WJ#W0`$s_2^>Kx^e{9)6rK6%Xyo+4r~uv!HDOQ$&m|LFN)qS` zKKIgJIV{d}0KO?$ZzxI>2%#&eVjjOQV_Y%4ssB6L{Pi#jP+;D4}ZUag& z3h9V^^}>Pmlt8BT6u-we3tnZ8Ss`)mz?EN48cL`YB^~=>`a^tF64q%vc42S|6gRuWryiK*1n`} z%$dP#SdWRqFL|T4{aWB3c!sR@`H{2Di?x+4SH)sU7Gjuu!9TfpLV&Bit)pBm5lWUeB#a=G=Uuj&~1UI;yspqK$vnQh*e6zY>)R{Wr6?}!af3fupli7E9K zAaO2M=nE6wDF(UAKYb#xBoBWjv(I7B;^!(h*mYv6REZI5(E;A<>tbMK$opluf?;R< z%vL?tIsu7N?LIHKEs^2xCO$`ZJF78*sPfS&^E$ z(Dhy7^?&z*+$7C={BSPaGDERKLl8sgk#q=lj#Vg+d!do;XpzqTb`cz1B;E5is#*J} zU#?TCB>grnf-SAtwk665Lh7hxj-ST>Kogj0_ge(=)X*N|mRd9l&%m8~xq@yg8G;0U zz*j>*+2%-*6O{6hEK4ln0>g#Nf!dS6%QDCnYafW`?IJ7f^S0kM8fxs}v;#eEx62AA zJR1K#2InWLxCqOcE*?McZuAzGBMWiVUwhaEQ=}23F-1zTb7uWE2DR3w40A9Ro;D1* zB^`7}6H(;9Z>*lw$R1DZKAJ8>b17ftl=_f-ZW5c~8!FIdfU>}1O548`xxxic^WBQ8 zulC}u@=7wV6Y#PDRu|v<&OiqBViFOEjv?If`45pL=|pLPA0BctBGDV`=7m>pst*Uz zSK%3zD=7KZ?U~C`56Kf(u*6GT6-Kd7LcS@-Q0l3i-b%7B6 z4ji)rZ%H|Nvwv>bPM60GL~@3^Chgl;Ww6wkMj0)sKK1UU(RTrF-XrG(Nk+*dW4n;} z_}4SIuQ6}Nad{)8Mf;eAg$cAo5lYu7Svw(@%G_hra9l6}B1>Kh+(h{Lbq5f$EJiYDzAgu;hhj`}8PDDM zOEj^V;}6wwDJFeXQH^Yg>GY%72&9YK6te~BYv`yU4-(L8O%enf!c@K3t^q`0O~_Q6 zyTJgA0H{Dkge^5$vn>3(`!yBT>E7PuV5h0vMpKcTbMUr{KH@U&WSJVFoq5P_`csFA z{EcsB#mi#_BCUskOvp4fS^ApncoXf`;!Fjq*ISAYouOBqnb{V%cf6Y1jL#G&fto^%AIQ#}kzrF@ElwX$GbxMQnH7M{jN%CK z9v3zc0@bfD3t_yVN7GdxA0jLhPSI8Pp1T>Ao}qA%{+>+zbmea_F)~Lj`oBudo*(Z3 z&*B3hEAuX?tBd_k1&lwtWcS{|@##Z>~&2G!wfSv(SZ65qm3{&?#4e z8L!@ckxNnL=$ZP9&tcV9z_-n*kJPilan&C{=&Fb47Q{5mG?21PWI%k(MG3xWVUxQm zK1h%GOFP+j3q<2wVHl3X>FmIL$f23U;cyXfzbLd4{5Bs~`4p*%jQtV$-4#OW2 zjUcA@BI*=%=*v%o?))YK?HFKIW;y`jD9Vg0H3pWr%21b43pq6|^p`E_x|jgguv8bi zoRO|I>IBxfn0koGxQd((6&9TQ8|wFEI?Yz+I`o+6eYs9T$u^O+>WR?sH%oIH%2a_H znb2=HF>ymRjWpIY+7jL5)Wv>nCng1QDc0K)lT31TML_6_>E);rn*lbaW5?5PAQu3b z@Aw?s+fph*k6Hb?gNQw_i`tc&&7k2%9G7HaCoXl3 zt75t6QPK#Cp?K}6K!~<3$3=6W28+a>RCdh?@wS){0~U#`hpq2JZ0@!ERB1imgrqf3 zZ5dj}E*3c_jr=__EeQBHfkcu>@mQD+Vq(3#BkTM?E~4NzMEZ3$TR_^Ko2bIH-JU^p z$M%)c;exd&v4uz6KIYC=dZL?)s>RMHTvM%?+09|iM@Zznb!xn~+|q2_@Ob3(ngvzw zfV!wmihxOI`sA8rhzjDldh)i{lY;51p-Qex4GT+B?yg~!Y4w=B;kO*rGFNaL+d_ha zQ#kBcQwkRr&F|iPtj6U`w)2koowqP(3V$O$wzWWZ`f-m=%I7iXb&5&pe)Lnyc3zAG zxc|zaeLa$y1St#%sN4Xf7eGV-g1B&sT$f}J(97a#;VLL+Ec}o!M!VIY0e*+eSH!PK z64l&sin*BAq^J;o9{W8n(%zcQZN4GDyjA%#rDS#nw=Yrh>`0+CVECLmxz-$hya(Y0 zJgE6nx6OoCT}F@4xA_cboHb8;y_fqfIrt+@$oF4v%WqZ+r?{9WX(U0$dkeBVO&*) z)ys8RziQ3IL0=w>)jDCZUo-@-_v0MT+Xtn3X1JlE)O6IDa`?b-Hr)fplDS+n*b=SD zuW(vCg-4QQo^#JocaUUk9x$$kf1!^uJpRoRaWr*a_M=>xCspUVd<@0s`RcQ4cW+|_ zkJo?P?FkFR0Z)|;l);PdH{VZN;tAOokuU&Z~cOVlMAOoDH**7-tDRNR3Cn zU!AV&&q)tUy;42+Ol%op1C^s#cG<8UV=3|#kf*5ErM(>O{9{N*5ikG1lrbT826p{K z_&hG->M#p}mEWx)koP`R?FBK8fe5n`xz3M7ZGhnlD}ViAvCe$#$)~sb>V_dP6UGj6 zZ>Fv2^>)aphv-d(H1%>Ke)DF(2A_6422Srz)WfYEG>2QgdmEIcON>SR`DxN%VHU6k z1Jmak&V3$v9FxxYcwxP@!{)kpyF2YH$d6^s*v<5_n15s=kX9qQ8f0S1nt@jpR}qV& zY$`$gILMWLT`_Akf9+fy87gK@XVatnb?NpjL@ZSA&#tR}K0A8Opi}6>K&+s)ZFEML zltrD9M$YLLCa*mf<_sV4xUjg_t`FUH0OR&^i?*Ri+W`DkSWFkrm<6@pl<65H+1cii zYjXdp!f}kRdfT*JiV7(kGDpf_e4HU_0K0cp4_VHxrVvN$vZjP(VLF6!J3<vjdUqZfi7VCst>UdFffuol%2Gyh z<{N@rU(+ng3L{?)d!%8=Kf$C|<_Y@v4C}}9&&3J`CCc~#H4NsXK~KV8I`=Nka9XSh zdY$71v17pNBEz377@6aUtHl;83YqzCEzd^c`b}FyPRiZ>PHv>oM!eyBlKm6%0;AhK zFCr&9@MLtBe#GL}yJ9ESe6A(cDeIh6B8Ci=bfPDi5tOCW+xW#rZk&?fveL6`@; zFrL`y>*?2ewsGGJZhb+iu1_l~1wQ+xdg=c8mrn)QdS_(*q#af4V7C$yC|+~}h%88< zCM`S5zVXMa$XT%a=rK*EV;o(2r(0KlpszwN9mL;tVS4aW2Gg|$-mntI;PJxgnD;D- zv^#T~S6b3@=OSZ@mHZN)dP&f;uZ}1xPJ1y&*yVhq7Y7D|rE;5`&!S*JAWehA%z>}B zbhZC-hLPaz-@kP3_lJOk+@lAeX7tURR;v{?HGv6#G0zp!kFxOMdxBM!@a|~_13NX& zmZd`oPpT(%KOz|L7yQZ#O8k=f5rAU~CK_B!KI85JcI@DXpeAfJ5h0=9EJkR=NoSV) zYTq5s2&vPbzAn&4ulnRNPZ?`y)Nc-K(gA*PuhmIpPnEL57xeXKGMPkQG^;A-*NI_uhIT*Qx9pW}ryUFN%)36WxkDefzN(ryxYUK!sT z)}@W-`$OK{lAg3o>t*N!{WN`+Dm^tM@=i66G)T24f58uP=y9F~^IN8|zmZI=41$zp zU&CPq69;}==#5VvIU-qpEZ8&aTNq9y#9%pUbwjUZtM0!R?-W7C45NSi4-7Ye)g*-W zL|0j=uTl4t>GF``r(@{=r+w|tj^G6^HrR6O?mYp({9c2&f^QdlB607xp`CQ)vS+Z| zJNt*bW-#0kWu{pi0{=qS`sT{@VW$(18oBj_x8q6mXy^T!~L=R>N2amx%Zgwi`kT}G3l!j z2{sf7!MjJnumjoCIZj$Cm3i&dmBG0u+_D)7UElF+opEin2><5R^s~cG{1qvvH1-Mm za(*}yTK7;;D7U%CN*^c5hvP;F#A@1jvB7h(drzQFT70fj)>?cb0|@0Oc0xrxYaGgv zd|BySHFwK^?yi$zMgg(%lUwZ8&0>cy*c{$5cfE~GtlNG0nZh#U+q{ldWGRUy zb8i-Tz2b;EpXgW{o2Y1a7T@9pzM((zf~`V9{HfI=p)seLHVu4e7PV7JG z(~%tUa@W<4OkZb*Rmj)Tv}1b>C)FI8k+k4RSmwWqu03o*Nq)!%FR8)bWZw2z``4O%8qK)D3w(PbiF6?BFl_1xt2Qz0>Zyrm@CKl*cI8Sf~zJQ%(8@zK{1CaU-KpJfd_Xjpk zS3&SY9bFJYvT@nn69%h-_Q8NO!~2=^h`-R(#K>ja^lNUBuwJIIx>r%Nlhqi!P zb_JKTlmP^otNysGrPf{grHFp@Sic-55snWp)FN_ljP&S3Fbbs@Xeg$;l0K?{2*OM@ zUk^Zr5&9`CHgR4-)pEEA>Dsn{m>eJ|?E{-+)Wbc{cgp0Zk~wC)xMceL$+J0Y*WZn# zumSebdQoJWnPW};XI@Zl=83eIADRsg1=Y|1-zr7-TUq5CE3c*#0_%rK#A=7{d~GoK zyhM;&R8K!`u`{W46_E&ME>$ixp2?Zt+STr|giGREqG+_}%+>K(Wi_dPTxF>GN<;4X@>wx| zTEZDbHse=f>{s}JUUbg%X}zj(D)^3qgqP}i#SOhA9H<7yHoE?PnI5M*?JJMfYOv%sAiFV($__4~H>2Rud&9hK zV!#@{^LiDW7}HyCnxS*DSM8FV9OYc{iC=GYhw+m!XA<7@Os-hSv`)>uMt8&LHg5+X z_^P`ZhinvxepzieBAjmeGc7oA^Nyx~(B24g5?=7^fr5HSxAdi1a8aVe>2x8a(5Xzz zaUPQ?2Ey)LpZq;z{QYdTv%0sLDEVh^V3iD!xHyYj9+!uD23hrVju-q0@MvD|@pk8I zzigH%BpgFq`<(>ZY&GxcbSp~3)aSx*nE0gO7E=O9Pz!x9w&2%5B1IkGvks6wvt+>! zeP5|;fc@-N8v|Q9Ajji~N47Yy8PjsWgL&?|!XnpO3Vc_YRLHKKKdD6N3S?C4#Nrh? z^I3*Dli?eVfQ!{vB_HJG*8#haPS<7lTgKMCZL&z7Y|*h(+qso5z$J4~=^ORD;rRV! zVQMZ_3wV|*UV42Y$?5=8a1{u*xOrjo=DwjJnDTe3tyOoI~hTx9`Z;WSa zt|Sq(ZvlyhyT%_?dJEr}PGUL87gK=ko91qPUB05d{g3x^mY48dHU)ps-aSGFWCOl0 z&plbGXQF)Cez5_^*@3##&^#PUHD1gcoK!{1e!E2+1)N=z`5euuWmRPS)O#+k{uJD1 zJqk!k)}WN|X}`6Ljb8PhbvHh3L@M~&BDM3bTQ&%OB4b+XxV#`-To!aU@i*1n?`*m8 zO&7uO_14rpXVWD!7yK}frTzfEdcb%cx7mW|y$2(fPajSeI%a`*vB3ss z^%~~I8m*!&fXg2 z&iA=hgV3ITs)LmmZ0yIBlm%YF#UIbiR7vDyS5)zPEb&PaiNd#$M3dEesbPVQ&WlYX zwwt2rX!K_ZIGE_4bSWU9X{7@n|KJF@lQXQ>t;*QF&n0q28&aCl?AJrHPN^xx!IY1EGDx8yUXmhsPEmdz_S zKp>&~(y3*(Ie+vlc1km$BKk<%3?qMGnnc~?YmHQYys!?uFHq;S&nc9CP}9qGe#DF5xIZ|YU11f+A<(EH>89(iH6*@v1s^1UBDoF@?U zDLzp|1@HZS1-@>ltGL^1(AoUZ3L68@2cA+arZYRi(;Ks15k2fbMiuDAcloZ~i%!{U z#2K`E?^c50TSW19n_3;6@Ar{(_sgiDi)dO|WEY)gw%C503F>mGne1VkOg2qwKQZ@y zif((xCFi4j-|b=UBh9m01vl!I1QJk-{R+7c;KJHbb*N@U5~y!7b$Mme z5j~7ay7HPL`9i(~6b~gaYE%kNA;WBC5hDejw!FW!AexzPV!j=$cG@leb=eu~OX>^o zaTE-g{w(`2C2Q1vH3czS@U;(yu9yJ{28quNE`?RnIZ@ZSckzQsFYX=)zQEHX!PkSU zTDcNOt`Fydj}gwdySWcfz_h5H+ttM;%Tcq&=RGH10b+jf9=HQk-FTShFaj zx?=a$$ds^ne);A8AEPb7DxOrSOo(ie4J?h@Y2NrVizp^TA&0aDPHLa0sSG1{u=~|t z6;1A3%^BONnc|#;+6m{HnC5H0ikIIuVsu@%U|aexp}j>we4<5Fk#$@aUyZw=mSgkh zZ}DQvL!o=RHcAfh$7Y;k2FHtvk{_wDL1*p4vqLr#bfE5!4F!r_Zr_Q-obGJO6_)8= z7>uLG1dFqchh}zZafgnv&{xQ*JxTsSgqzP61A#@qL>JE}UKhw5og7b8{b%BA0>mY# z(@=suK*>xc1(;6PJ+ZaMRgb%SAXvU^jf1;zprQ z#(%pA|H$N?37$>L{c!NRnw9a)ZWo#f{K}1^fs3e3*?YQ8*0D@Jo0}*n=w;`0y-NCOpq{2}ENj(p_2NZm`C(pyc&@O~Av?e3x-(JnAf7Ikw5}%$fk!_xJYp zKBn5*to6b9AQpUc4Fz9p=|N*@{awAy==#j3M;u0iXXgPS+Ek*mTqluWY%LZ}E?dksi3M zucPm5TqWK84C=NMaBLCrAF7L&F&DT09jtP7VwpozRbD33I6DJ#E?5U_YCGChg|Rlm@DKs0&slDlJ>3(h6M)CI!$c&+}Si8rI30kcG#3lzSC?RYnQp-s|Oa) zRyM?{c3~R9Zos}TVB0sNF*2j8>VNpc2t(=Qmgw}AGCq$#!7!B&9XXvfv?Of4sMc)NR#fJZPL*Wdlk z>5&iHzSn{e%@P4^*yd=cDB!6}N5Zpn$v{W)@p%vzZf)P5*NWiHOZ7kE z0(AVXCmoWcUs#d`m8oLib$)ZrB!!&75NoAGu7X#OmotXi=4CQ*&X94LN^!NH{dw6B zhFy#wyyGFj^g9cFQ<+TlS4JXee4z6-lP*h4#_s%21I+@0M#?@Mx^jYAgVGuCWmN~q zN{`M_#kyS1hi0NKqhc#puXR#8!UFkRmpktdP|nL6SDz5xC}a;s*v+b*`L)7gAeb_` z{T!By+CF2dx;wWB;qzJxH=rA_b&huEj=gS>p7GeZJw{*My;_?@r1~C?2cSB77(vWU z34JOXK_L6C?x^6M(sVUWi&0UuL8-d0ove*39-h$S_K;iHs#rs_NAIoTV}jekRTR>6 z9!N&IJXolQ{q71wUhhe_wzY2Ib%@8Ks{gpoe3S{iTFUQmAg-oa6qCA?CIyLj+j!8MNUs{d^}39DKNygc{EOeN|jld085gI4n0-9V!3&BH3K zq}$eLY7UK*-yQ;WRW<=& z(4OSt2CrYTnS^DT#?t9!l-6x~Sd@Fue6CVS)3M$eHmzKmEzh6R{mP_b%5qT3uk}^Z zV`TOquSt!Y8A*mD*ELay5ECRKmEv!w^-hSEnfUw7$&`CX^^(x=!!Hka+xLJ}&x(^@ zs|r5iC*wIb?v$PyuMG7*nk@a%<_s))((u7phZ2uI(l_9-PDug=aDabH0YD^sI8W^a zg4}`)3}*fT=>VkaVX^bxM2fb*#uwXuzGfR>WtoHTFHJr)Z&#sisut@UJog4Hry97R zzH2p)6cV$QSyL1XNQqJ=S@OQh%6lL4D5%8D{_ENl|*$G;! zMwFGO8W_mfWJ1-47HMytdv@HbI;1WLcnSRXd!y+>BnG_S_#dkk%T`I0<01&+t{pYK z_^qAH5N5*x$<>YC_bh!Yzm5#kIt7yas|q*v7ydBz(N>R&$I7Q4-Qc7VMD%}j4y-xh zJ((_@`E;-H5~S+J@HPHVD!LrxWUtxLcJ1V?@UY5-b8NH2)OZ=kFty6L5l=b?FSoQg zS^Qen6YBsQrTuw9)*y@28uy(v*ev9>ht50*Hacq&us?rk5k&j-?{~wjzje6au&M9R z03W+EG{NmTrFQqA1Mq^m-6>YR~PQnY#6g}xgt)c{JqG$WcIMkqf z&jZ}JOaG3ZcgEPt6srkqqRYu)e*8~2ir&@RbPh@Y$kZ{YtAi={$iDT6L51lOwBM{5 ze}f%h`1aN56Mlcnvz}WG+!%uM%8HAN z0Zj^!V@JvN4#Wd++r2N?P{u|;C9ZXa<^S$;ma!ANBd6cHyTVMk?BdGEeRL4c?ry2M zHPR+pk0d58(AjwEl%~L8yk` zM&jeukk)uudj%yy;DhOG;LMJ+G z8>8QRq>3~FD5sEL4E7#wkF?8yOsHs3!kPjlT{JJ&N2wjhc@Eml6hg{PWv37!JJR5h$Q0e8`?XE6V*N|vf~!HJ4C z7SX-=&n3*klKqELv-Qk^H6tT@tS37Lva`P{1nw^3EQ@2a?GHmtP#O52D9$(hvsOMu zCj-UFzs_gwdfq#RvnM0fe{PkBfwb*_7U9W%3MsWGxf8uFt-^i)SoR{K3m&IcN$6(L zrR1NHe=T9AF6v6zOnDxK9u>vTU!1QH9;jy<#tYY)Yr3Y?`XYg?wQe-=fqvof){awp z;zsGh@9zN?klI?E;(}ZWUNRd;X^D$4L%{?E`&)t|(D@9rSvT&ZR4$7zW5T7=0%gvu zI;(^LIOXXM`Yfp-5Bdx}$k6`$kbr634Dh>9UpkRrfYWIO;2Cdk+i1z&3ssT5v?~Ve z)CS-CoUAOD8I&)6X#Iz$OD3S+qiojfl9rNJn9B{805mk00N?1kLhU z)hfN3(lK3zvn z?1@O|`SosY965ie*8t#f2t*+QfRD`09@EyzV&lnns)h3KEZ}bVj|ez83ZLd+JRieD zpt-`qqH?~2+{ zsh@zFqWt@FdzXwLoxk1bEDzlmsfOp??`U3TUCU(l&RI7I_IQ-=iwwFKpUTBc{=F=o z3wk`x_+?aWCu(>|45P}DIcB|*EoQnyGq=AZReZ; z(|i|{31x<6_slu>km6$d zaQo%(eUH4EYmVGA>aVC`lTFYxY<;6ByfIc~oA0NoVSYp*AdZC=EVA%fL|#Fvf^vbULV6nse9c`x zG$X<>Jl^@@qtHOfN34Lg?9-MuMBd|-90ZB>6DMsn$1~TMGjNj1s}g*(^0FSFEx~mQ zW}Qo$nnQ9ZrX-!V3u^;9@Ux34E#nj&3%W)ZBK$>wod_h?Oab$17aO{g$L;Z>zlpzad2zhMW5YsIbQaqUo3o7kPaWpL=6@T5)ZOE8I zyeml*HY%2*>$*p?bOX5EI4OoWb|mC#3)_%Wbo*!7%Xgkp zaoJ&@@fSDOF`$J10_6TVahk|2o`KGTML)+keJAoGZ+ITmanSYnwy_B|X5N1516ye| znKfSy@amM9Y~M|Om9+D9lH`>Z`J?^C+W!0C00dwY=>n(|H)g8aC4qBar9q08`46FTQ`^fG_&bIkM2nwFwRuP8& z%ARkKp7pUcIu;tvUPqdmh7=0DFCLCh%{c+gJ#L~#a9G$P3Xn(JHZhkXoed znXAF4F*#WTzX$F;A*L1;)mPW}Xt!u1f4yl^FP!&^6O&nb$2DgYnOTyCj8h669}z5; zesiFrV2_t*GnR?6>#e6Tcr%_3SW9rs{#DiiGLrxDAsb{grvMG1w7%cVFtd05Q$qO# z=$1yLBiaTe-u8HdWvAzo^7i6plg)-FZSb+SYXWPBKDFo;Ifl`$%@>jk@S^t-}n=Wm-)`;7ZAYp|GHgyI51qnY zyz=rkP_pT5c1`|n_+9^dZe@j(o`Lt(#ePqbO$k6!0U8J2X=aC8zRi@|3-3jK6?uHBZ zr$`1|fqVfo%(WSgZ16%~GNf|Oe@93y>4$A^SQV!rZ>FYUcc-;RW!0C9gJo`Go&Wl| z{(OQ`XMG@&s5Zn5kfUrd(dIb*h1fAd z&(mxu{78>sizjROy)`#!wi@~)>BaoZLbho;`aQIEbJn;{KP)vxDxS6o8#S^XTw#8; zeLXqCo*^t>+xK|r?I>+!#%2PYK6$^Vwm$O!^HT}tbZzFnW`F)lhZLFK8av7{WZPW= zjC+6lVHN*avg5Wq^Rv*%TEFMKjPwvRIw~wXeBYc?VgCl%Wml^9g!pqhBvj5p zm-YL`ofW?>8#m5tiSj=zj^CbVc|}ehyd!>pUJEUZ{HF1O^h>hkBOuK{#^+*f=j$B_ z-@}W_C!nYQ86!f!xM88u6Vnqtn2ThpSe~o|$J0nV?~8Mj)o%S{pRpjRJ~w=!nGt2R z<>->0J8H=p?JQtLb!}f*qj>vwX1DC>Z~?~-m_$f}p2TV)hk|Fvhi`U{Nduld0(bKCpUbT8}Ze1<~;lLdN3oo6(jDFX*iuaBiYv-dwMx^CCMnJ7A`*0QyeD!$2$4m3x z-u2waO_czbYqcUjecp?Qw)`HrTAN+^uii@(QQY-bv(g5h@3ZD&Up@8*W|*xW0$E%a zAXZgN`f?iRDsqCa=381?uHn0#cjvL~?vNeuL@#%)2rAAY^q7U9}6yrf>Y~baoG- z>9PdUf!=1dH%Uh(jSLq5I#at?nt(>#;oMq24{URTwcnz7C3&Jr-5i* zl71GEcRYoo9+uDAT7P#*<`QIoq$QPqitX<(9ts%tJgdSYFR%8D-G1A!BZ$NEt9WNE z^r{n;FL`ozsTeGQIJko6m`Pq9uN3$~8>9o4XA10wX@N0vZza7%df{?!g~WL&8TubhTk3R><0BQb zf+Q)_HgTpVddrQ7`w`3i_mMkK`8q>=A~0c|=Ir=9rlB~TLW9k7HtsUwD{-5ra{3@J z(;mmCm2tsH94nJYkIs;+E69CW(p7n@Rg~PS)|*akgX$~i>ny=tbDpZZVE^lH=QW7o z;gr15tZ!MQN>nvd9V5p;`V{RS?yFG3N9J+9I3=IA`s8yMD&CI^Cx&T65q8A!+5!=J5z?ngl zP2S}o&(@6*Ry~d>K@oi+iDrxDghp(}d#Cx2VSZDh^=4v5Eob$nFir%AD3iQNtl#m-tHQP@LDs~S)blqacF>gEb-?%a z^?BB}B;X%Yv!p4$POqcQwLA3#1YlF>|H7`HLXd>^I|7;$>oyndRjUAay>4jy93@dx zej2|PzA=zk#oC)~YMdd|CTG>4zv-a!nL(*gLqA0Td}Rlg@movyupDp!2)%*Y2kxo; z#a69&TIoEdc1FN(C;E*ShZ-PjqoSl-Q&jtgX;)*kR;`D)GfuOL*))9{NVkiF~$ zUH0ACUdl$SOqE!y%7;Z>z?4@NlHt$PWco698MB4U!kq38{-`Un#bzC zZKhB91Lf!a(mJgX6`?pvZ?sdCo@*IGsrioius=5s_+XcnCYGiWVAle082%fi;TZxZ zYU<*0Qn*o-OuIj5Ac?wYEX6Stq+Q9>AJd!iL(>D2Iftzg(+v`)aj%a}(dz$U;Jf6$ zSk2Vm_+dHZc&YjP{Jf^7Mh~!z>}8qcj4)3A{$1bDum{g+zQzWCcN5HGfsBJ?z{K3r zc%c0W;EkaLYz9gNX8KtKRN|hI)hlc+yGpt}vOJ5=-M_T}ton z_l@*3KyLZiKM@PRO!@`YPM9i0F}k;_r1b#tO?`E?H-|m=G>eA|?T92TnNLZsiwzFv z@flMM>`16;r{eGS*)XkIC$u!I585t5K0v^|(il|=t(FkAc);CA`nM@oEr)Lvy9O+r zPA#S6w&uEStaO1}6H#P5RvllGRg0eE;dyH_zXAZpJrT;UIa2-tiK^O!47Bldl0ID7 zudVk?d7_HKAY~jQ^noC+1>wlalktFWUBno=#1TNFW>oY3xD6|I-XjIPv^=VEAG9>s zE5-idRsCoT_pFI74W`;p&%^>l<~ZU;c>Tqw-;G}z=PXw04L!{N{FN0CeHzmDBTxVUg^$;` z4j^`4d+v^z{d$M?y?g+W&5yI{?)}5?1KeGJi!<x?UlKl zr_=C7gOl+i*mq>Q+E~+3P5+=2J5Q!kvi%Omc=_S9C_LBcoo*QK>{+L?E~b_0Go`ZL++VM=C4i6@&N;L{tc>V# z{}0Wyx|75Q^^TYtUj*Aq-F{WPDq^5DjK&DKimjE&>ek97#iCU610z!o$OKqU0F^ zngw6!zyt2w_Mh{$6ehp#u19hopHWEJJs}a$&@Pal>NShIwtA2z!Jt=fkf6*`&g ztt3vs!3yYB0@3Q0pANOI%G+aO^6)=fz)~*i8u5EwYtjP%ngs+hNRaxcjGw=}_I25s za07AjsfE~0MXyYokiKZeVM@O*Rsn&kLHQdzz#1*xE~l%OmzC3nrO(5i7BqyFNY>0S z7uHjB=2Z)0h|}Fw$CGg$nEmoE1P1xSv=&4-*oz@FYdm5~BDk5Nk5xYHjAtF77+e7) z%LoI#{mDXq5be${YAOZytg5d>ZCB`>IC;Yg$HEu|@5BP`0YQ!+lCcQ_3z9HIrxf`D zn9?Xkjgu&?YlZD%#$$Rq6G8uk5#Y+WUl;)2kYsI;D`Z#8`_AiY@dujV}A=VK8(XkI_u?rpGb zEO_u{^R-Fk#9t;w+>BK&M!dsLvre0Yv;Y90^!>$@-$Jq%)+!qF%tk!b@LzrwT4gZ6 z|LRSjRnvdaC69mD+Uzh@2QOt{e!Mzb<@tBra5nd$_8U&2Pso4ZuhX zqRs@)YbbC%e@yB(w?0&w$~?NQ4Hn@{pdgIbj7nH(V%Q77v92}s<-q;bz8@!=Qtd0@ z)pR?(KM8n1Y3v&FF8cz8hl@yd1WG%c!2c(JpSZ8wMffe$J13XS)&%`Ya(c@H_+?3W zNW9Oq$fT7T2>N(aQY^D5#e(F34K z^R@>4?CH2)Y;<0T?ZiON|8MLO^#McfA|C#xBO@2{{q)W27LhkIrFyCMN_a=0jWawt zyv;`8Kw`F~*+CMEG$EcKm2nF`f9XFdHM}mv+Yu{CUrEb3a8yT;gKUA^pKaHn? z!NsuAv%BW60#pi?^zox@3>!$#P>kpuMgB_u4unq>zAv4WpC2$=M2J6)X*!L*A;7wK zOW13{x7nX$!6B82?KdBO(`+2O?I_jU2kpOGi#MqzMwK=fPX>WhOZm4P4dT`f0e7Cj ze@_6ZJ)J!uLh;uZCtDqFoOa5*N@JSSGY&j~qYfVm3N6@bx+D2jwaaVC&g1MSP2*5< zrc_xa%9eZ8E5G|Q%mmuo$Vd4opGGeD7FW#u!aH8t6@DZVK~}}Hge2Orusch*sWlT> zw1%6yqQg^t4!jV+tsv?=_b~!HpX8$gyglJvVn)99-B)%m#6N$)gJ~G8P@=R<3R^Wo zTFVi+Q$vL`O?gZjydFZ8ke*~6=sRjN<~&3CcLvcU>QewEN{~O964!Sgjo$Fz6K*fk zmkM-V$A-dfdsAWu6lsS4{K8v`Ic@gf`PLMEZgMaA;}my%+Q2~R2A~j5;nNH24O*ZLs|2xh6Rfg_ zU{wrSG}AMB9x>yB{F{G*6G7uE!HWPH;UB{3f89YrETfM;ihW8N!H|R4kv{&oW=bA& zC|G5Y+rg?uZK+Ysa!j0f-&w4>o_KRHcPnBz-Hy11_j%I zN(^LA06Bvk_%p7L$L^6}&Th%6yLSNd_ExmF0)tSJdfHG=%Q4~5RmwRl^1GCF_}C&a zJV*Snaip%yphM?Y+9UbJ*S9PL^5Vv_HgasjU>p0Y12nXLkF+=O^C~lY4S)X!sY35E zbejNauPL2^w3BG$2L9TxKLIWKR)fYHUm~f9$nZebz|tQ5@k1G5#iD=cnEzO+Cv=x+ z^q^21`O$-iXgZ4S3HB`7^ivyBLpinTSZ5I@a%I9h_mMTKQ~9^yV2qU-;klrTx#)P= z>$TMn9qk)Sk4FMuBW(FaZWqacZI8cm@$k;Vj#ys`FdDAURTH@a9*F?w2C2aJ;<0_k z8$-jWeOL>Ac1KWr5cbsghAFv#Vhr|r38MOCQ=JQ~YG^1nuy?|avA2AB(HmEz$zrGCgBaTBL0bZf-?XP2eLIs_ zwNSdMq`%Cf<_RoDm z7W=a#sUbF_8`y6jE9s1PT9Omz|1K(aU{iSEl?Fd3=S3W@INgP_yTzk+Tv0N{@TbjXemY@OaJ3{-}+^#tVX!^k1x8vnSz*V)R7H}%yw^jyB*nmt=3a%;}qjkpF*;&@- zPb)Bfg;iOZH9ZWxq~ysazQfeUyR5eQjsZ~M#?3kQ2IM&l$#G!O8kA(^YGw@FJKb4U zG3xmiE$591S4`ZrFAVlyoQCFqJh+B0EUGMKXRX($r|5sQhP?<_2$@WIdj`zu)_t7K6mueG(;eA z6IvZx9m~D{w{jU!8v_DHR8Pz5cKA;@t6NTbnb3li&bd3p1@}t$8;%|>XW|?vs&zCCptNL$W8~)~N z{w-U?ZlT0;Som0}w z)dW5Pa9&mj8UItOsWRmvg#pO^EP(&VY4`ga%fP~awxmKW;!Xg4&lEl0}^ba@oG8co#ymJk$brvRB~d}Y-^ zd^Vky{d5deQ!53Ut`08z;fy*qOUpR&n`Hz233NUrI+n#Ox0`b4iWP@IRdynBpjvb5 zCFmwy)m_Bwa@>MqQewE&+OAtD3*54vs3$TTal{aMxnizi`MS_uMYO$UF}G8|q#O)H zt^s{PD*$3C$I}J_n}fGp%1Y5M6=rAN*K`RuV}q7g!D~P<3S5O+X(8^@SI21NAhJDI zwvVIaI;R?r%z#bk^#fK}n*>fKc_5Kal0h_3kWF8|+gzk2%T{67-4llDfy;dtNc29P$~%v6P3 z0y7{2&`U`K-w4r)FE?cMRoVqTA>(Kaw}7KW&{D*tOev(4^3K-RtC`Q*RIEq_f8eO6}4cq*Iq6iPyq=t^tqy|4umI1b_C)_vP_R1`y`Nwy6$C)f+Aa z+5!*%Nm>)`WdprG2Wd-?0?wb+E`=M156hB#bc;_-wP-OooIf=$oc=WWmH8*M0H4=v zN^`V^8`|h>Xkeh3ERFMj$a>4DDx$Zbhy@fo;mOPN`BrbF?o5IQC`=6keo8GZ3j+ak!k$Kkpu{TViC6R zxDg{m4bfnemGN;O>D(k~7z`U>s+C%ovkj1s=H21*2CT$M02Vm;L`fF7Fo}+Bzh>6u z^>^j>sy{_k`^ew0Ab|(12FU(1V?|lk_$KP`p6rtUNFp94QsZByw1f?q*a7RRF{Vm)~Ox zJR#gJr}C`xU@K2zA=}df8qrr|a(7RP$)VdG0RiK&e!Oh=G?V(^;!wBrle|wbdK7|= z>yJa8!WeNfx&jm$i~Nb-7`*G^7=i7Suz}eE=@kZk>LHnfS7QG?b$O$=eS(0(Sq}LO z@y;rRes`_rdVh)m0pd81l_2`t z?{QIFmPl>P`R3}_zB1E2PcQ{DEjy7$WZ>lVyRv0u3Hu3w^G^{sC<~g1(5-UF-`xSd zrJ>eb%u%rjy_elzh6Z?3(lRkR$aqfy=<0nwKtTdz^_!7AnS@++TfiPWjp?WhiNn1s zC$z({?6j7>D9^V1eRzMlz#9d^O~}!Rk$qR|7C!N0Bo8!&7-#Rcf)<=tQ6Gpiq)+?+ zTayE)KbTlrT%5?}ITm(~Ovvv6lI_6?U1u_};R{Qk4lXlTLXYSXqdVsNS&&=cb=yq{ zRBT^=uUs!0^xd>S4u&<*B^zb#U+%Ne4c(n~M_GfQF2I>dNe+%L0tG%`frIK@H73s( zZzGY=PKVbA>^%u!;4pF1SR0!kvHFBb^AAmhu=}$ye|^f?nJv>2u!Op~1PXCc%~J1@ z+tY4j=YM~F9aYi2+Bj87{!VSVNF(YC^0F)cQF-C}hVb8`|BJ%y0fanBAOwm*Q}&L{ za*cBZc#|0!tapml+k%=Ubf`L4@K8C`ve0)qEGH>#yy(%Vt$(3~Rw(n_QRmMsG)hWE zx~YeAAU6ZCu(hnT!@b*&g4NKcm0~l|F6Fu@Z2bmnW;<;9LjwF1S2S z7Jx(<|KY!%W5=Enm_D>kJ46NL&B^Q5gLp=#Zq)A~EML(EDVyptb){3gWEwtl^H(|p z)+hOokCnQ3zX31pc6B3xmWf6TwtEFI``nzYf}R{V_~>?+U6>g@eO+Z6AjV zGQ1*(1RkGfVDFLr{IV2aH@8(ps%U4f?(`PkFoqyNWT||~%<2bglDu|?wK_B2oZUa5 zLZt9Mr)WrtA&<;pwLE}o3p78E9oVFh#o+;6+rSQi9j^x$( za%t>(gAU)bpKFWIC}chg7;c2aP332eP7f%N%Mftbo;Og%7@wAtXU#fnft%5BGM6fb zTqcnlF^*gi?mA2+jo%c^W=a$?8EmHTz1SR%3*l#InUT9+Ws)G0{LY9Ojn08?mtriH z>jLD{t{k_zGcpjsZ8KD_H!^z~jsNo-z@+>Z4A8AlW!z8f0MTIf?);SCs#(0CsLu-# zTVW)*WR36?%?dZAO_EhbOK{w&)84fzsD=nBsA9(-dDuihr>!Um;#37|FC&5 z0h`B`8~F?TuQ9iqWA}qggAhmVhf86zSJy^?n zuC&}_eO-?Qx7)!WKa=F z&Bn*N`KUe~u=VD1Z;(#0bUe;Vbe;~<-H-ihBFI6)?dzdo!{vDJU1QYwaQ3JV5WgcF ztGrfAoOZjZ*$Hvt6voJsC=#4sjp4~pW+q+9B!{ga0js<8o zISc>|g>HCUkwYE!&|IZQ9z{=!N8N~kgF1>>1Ueje}m13k~cUdrW;_XQ# z^Q$#}R*!_GmNzgDq?)VR?K9Kq6zx#wDmTWK1xcwXXiNeR|KK1C?DYwGMLJK>8?2_) zRlGLU(_?&^j3LI=G)BEHu+pui&;fIlF_wUURhJ7lsv!W%fx-1|JgXb$aKX9dcU; zQk~n%Wo05I!!G-*Il&SOWX$Iq|4sHU$W%LdkWE+bZBfHBCNP)+Qg~w)9F8{tB^e*S z`ng7QI^zr4X}X@!J;cVR*5#v$Gr3 zRUrl%98=Q6IR(G5bALz@Pu$rQ@+M?V(Xu_$Hd8Mu{e z$|US3<+vS(|DrWkU6Coc2w^7iTpEs*Izy#vPvD|L$fWpQbex;aZDM?|?V>7+&vtGc zr(6_Y&T6mXRxB+QUmU^$bi_p%R>c8DSLLWGN*JQd`lMr2MbvCcR0V@|(ufx0iZgE9 znmV?aEFZxn0C+k-+@AxdbOD{R9NaKJ#^@9OT?HNESYbi?fJ324cjfc<N%MdQHtQ z@)0XDPNvcR>)EsU@Or-8Cg)3ygKH&b=&Y449k-?PO+k(Z9L~28?QrrLdO2i z8RbOW_x!fuX#Kk_uH1O#l3beHAZ8l0&TQFKPTTqFJoO9@emSKJKV)BM_wCzDkaIK_ zutuOjd;IuO@M4F-At@(!EXnOpT=CbUU$FYI$s1ooQRKotCx7=sgLBVG@0QXkI(r|S zOdP>j*Q-|SNtP`YXT~2XP?^TK7q^1g8}uoG@*E~~brWnDj~x(f%yS#h%S^T0 zXm1Q=Fc?G}R?u6)WY9Z3H>bqE6R7+v*MeQEHYZqE1cFVNGPp}H0*Talbihj`&eLda zPO39Zh+y@N!-2|9yDpc~o$V0V`9|7_H>v{LnmaBqU4a&tEAZ~b>7UD3nJu{~Oc;r7nQsrgNb+%}>NZALmp`|Hi?H zw=Cn+4kHfAVax6=2t?zPOYLeoV{+P*yJLgafToW|TR zOM=OZ8iihgbOzflki73y*WqB?cI?Pm}v@}&}K z;C(cMW5Z62k>OEO)LiKkRP~0xp?Ul5zbBOwug3vcX17@&-Zub)j}Tsm zJJ=2Phql3ZW=nWFAx2pCwfQ=PBK>yx#EZjxwiNsKZ(Np$wy-;$mj;NT z`m+cqHw3x2m7oQ=#Qk;Ui96OgRrxbStPJPpUtJ?(&vQDg)7HAr@9Q~I?k;$}taLea1QNl*ZXJhryX#?4+s##T^A%ryK~x%RlW@Uxw^%q871bT#%ZtGNeAQFx zN2M&Ff7{tvUB0}~d8~ z9V4DliRAuOmMv{l4_H)(-M>F~_fN;D01;Xv_K+%5UL>I%gP8RXPP1txgGVB>M$Cj- zmVN~@)<7iRYmoVDb8Djqi;}s@Gxr}7z;|uvRZ*^r(N~4=)k)jSOw`r&4#ZqkTORgW zYSnYl@UWklsn%Pqd>D-XG+*^mCi$nOmecXyrBsXgg;I-d6fLK$?y^NEkNZ0cSfrEr z2E*kA$BgoC7ZS|#K6e0>jpgkqku?}2m>sMiktPqsyFIE+$RS7Fq}L?w>YC}zgtFGx zfWa4#)(GmH(}>@G?RdiDwE5g2M(Ft^PmfJz3a?LsJf{E69YCGb-%$2YvZ4qj*tG1U zvPl%6Cx{pEhj*getRfTgP3C#-a;&--FWrW?U$^X47Z`fRS|LxB4QT%hkqJDnX#b0b zVEz_?@gG$&|FDye_3C;g--DC&Qp1Of?2zD^9CUd~3hik5H$86SP2;4~Rp+%M@K3vT zALH83`hPMF z%5ZGuid*WoSvL1@bHZ(LOT=4#fjLaT;YC_HNLqYer0Bp8yge8+E8v9_;~x{?f{i(~ zCo-AE8hz44dzA72u&eV%i`6^mh*GnOH)EZGvS^=~>}!^xWY!YOgp#3A@sg~nW{ABg zs#{Ugh9p=hBzsQ3fHth>wazqLj?$hdyRRt(cFAe~v9F+@U$7zh)?=_8x+Nc(*j${u z&W~G2u^~o;mE-heQ$2Poj$~gj(?13#eo|n|ah<^UMSjILW2TBAqedDht1EPv8ap!7 z2;%d(JF(Q`MOHZ(hEJ$JxamwcTBa0JA2z`YTh94HW~i}Ei<6?N6KU-l-;4iJR-20+ z+Lq^vlY}}P=Haqg>j22E?(<+A4RC|667ag50?ea7_)OFW0wtL;6fCc%$pGr4q9)0> ze&&qymtIiwlkOOSPmX!pHC*{NPnlvVW)e94uJIQeJNru9uG$zf7JDDZ5?_!mRt=t0 z!dpi{m_X|+WkWzf!kos+_Z8F~hCrUN)Sht9iDtDZr2?tfZAfC`pI{*byvv(p$IW{p zI&l>8wTUJ{G2sfAP3&211si0t=`)X^YFoLxJN|}~(BvE?qw4wB!Y*53u*SN&hw;R> zx9fwvYtRL;w#iD*pQKPy4#a;iu7h_&J3WV$~O*5jP%ewFDNyzk|;cKcFC3$+Xt zyf+Ci5AGSXo)3Kjk0quxyO#5nXItxR?X7nl>?|+mu&eIYhsEBHJq-2Mi{wy>7=|_M@KP89ZLAy|h^DpX)sB%t^)bPG)T$AtU~K z`M0qtX zw9#}T$Wll0*>C(_mR!~q+Z#pL?s3<=SZ_VJ?SdXI=B-Zr_KT7g^Vy-GjqzP(P-m2! z*LGY2fd8qyx_O7L{Ov~)cTb}dxjN6g*Sd_S%TpRWmQeO8p66|g*H1ib6X5WYJ+BkU4H@AeC{=xhJT5eRh>V$9M@UIO zDhevti?rry+DwWZq3x@ek-Na}sdU^qM#X&rRL}1*Wzysn`}Veor*SM5wbAmMfSF*aR2x$j{6^x6e}+5uTR zZw#c@b#-<92(X`@fB-Kja?>Okvv!Rb0llag9{=L4d||AjvLJjYEIdmQe<gK&hv~D~VWzcp>FY{Nu2A-_&yUNI96wqajYfm1z}1QcfeIhmJ$j!9&t% zH`oAa6B>mq9;0qYu0%{BC8>w9UnZ)UiXrPCmayIQwk*(gCtBf?lw_w?W~7R5FqkWW z78HeDcN})bUqj$-an>bbFYtUTJW>jDFuxVfl8iGh60a6;E22^upF@n(XDzK*U#`x| zJibv+fuZ;|7>mzVr^1WJ$?9ACaZN}J#Ykcmk)l$S@y)fXJr?+;yU(`R%mskdYLrDz zV9djPGpNqvha7)li0T~MAF(KW7Q+Q)-f2Rw7R3qs<{JyG_Gh%6A~*Kz{L#*7TLTI@ zovQDPqcV-W-ttTpx^d#1{Vi4j-9P;@NL3qZYcI2c3A*I00R{|6q@D`o!Av3l+wD_5 z4#}Vh>_~_H$}AMhZm~$8&#IkW0xuT=0uFm9t?S})Yy<*r$#c$p(ElUl>-?QT^A6&t zk{(WZK2`Yqx61Aw|E?Dm-rGJq&go&KXN`p%@9f@}_b=g*vM&6*2A4y~tU~0$nxBR? zI-%)@vlyJ9bD$mN6R^(K#dl8>h=X6IIGr8+aKay|DB(r7>B(rboV0nze#=i)z5m92 z5_iJ02D@}hOb^F(L@Yxe(xHja1LwBGjR>{+>oYt0J3%U5R?a({lOpEHFQ2La`z;cN zxoq2q52~Daz>y3{fM`NL@DpCakvfI@?=H>M!t$)C;IK4@bY9L4*VRaGnbxqhC~Cw=AWLf$0J&Mx_gPs z_V(3f=eoV=#=~XjRX(?A`M*8>S)CV-4y#CyiAljaVPo2lf3q&g%hS$NS>3uR>lK0t z>BfRTd|R=PQ65pot?Xz?P6*a7ag5i_F_dIltui1lcX>IoOwK^~O&moKQt^)(s=etC(4o;YeXgD^T$8CS7KP(c?CCba++3}ykL5TSL zCLNF@!N?val-0qAxEwZXKEXjqc-$`i<9d){$T<8a{XXmdL42$Z9hG{*Fi5zpE_>0z zFh~TC)2DWK06o}pw(8=W+w*{f1VXge$a$^#OkDgo{dq(_#kZH{2^m1nnl%x-D< zce>u4xB$M?-xl)$W|Rv}jx4c9SjK%w-yeVz0awd|WpH8&u%(mCNVV#e^ zJUdA3diI6r3!N)l8pEctBe!XA#Yp~lO87v zFLxcKgAqX^9u&#uoAz+n0#Xndh(MVl>#XHWv5MXY0@A)J<|G7W<|OTJisSo9ECsJF z2}L^l(K-JU;i5Xp{DuBGlYKk6@`-8{$^1Y4s@!HkIchf}$);o_ZkVtAGlVcAbuo%f zx2?fHX(Ee$3-Ask-k9AypdU}j4_XzOQrbxD5qrH zPZ|})f}h!PQbo!&yoACf`^?Hy_Hm58GaE*2ibJs*ZxEaz^x!K~$Jqw5QHI#i&s z;ypD`;)tJV3<|hk)264EX`P8%gE$~Gz9bHm#5cH~Sx%cf7~Us=_o16G0$3kzLA_q^ zd}mG z8Fl1wJ`RFyGv1Bo;{n@Nojncm8-QYYzHdovU8>ma^!v*5+j_CdbuKcku!iOb!M~L{ zgNJ&$2PezbR?o%bBc5@K-D~`2=j`>!WghnKlGQqUfrFE!D%Y{{)@G;7SxE|bvC*UE>ywol!;9Y5X5@dX?cNWOGQr4%V5fM{JKbjZJ4V8sD76-k zW#q=xrAYZDQG(trrncd&;+FhnfFgvFTE({~@ZPV0>P(DujV41Q_)1oT<*rtB%blvt2xu=3}wmuS@5AekU; z$dD2KvSSj@dK|C%*Uv2kYFJe&(0jxf?!vn=-XZr)Pet_+Obno6iHXgDfcsHB!e+v+ z@XHigE32}A#mtwiI4E_|!OmZ1ou)FiLH)i(4r01T;P?V$SR=^qSpsJrXy~TN`x-$p*`(JFm_0rQ~J*X>hbUz zT0(tc`5q{_v|4Umw_Mrrki8NkH}mm#79LF|rBCnU_N%_zxRs5pB3?+ta{4@p0dP|a;tBsJKCxb8+$u*AQA%TXzI{YV=Q*Cb+!f~aTOCkMRni5pe1 zeGOuNcte;%&LDl2m)9t0ZVne8{+tQ3&1`SU;>L%3gfaf?4V?ukC;um4a-&1&-2tRa z)OfcdB^V9d_p*X}VQ94*qM3`{Do(MkTW0IgCkSgnFigKgau znc#FReG(g~wZazWDU}Xn8fO(Zq$O~Q%*4Vd%%$ka^0x=2^L`P|8<2sJ0feH|{i}L( z$E_bFU-&StT(t*4T7_!&MV^~n(M@e(5`zIu6t|xIJ3{56Iy+vNt*uY0`KZ5jhxg!4 z+HQ=_kTP{;tfV2{1&~Z0WftI29ls3?^|mdPAGbpJZf3%WF1SumN{p@^hVCt#*`c|m zB=f)S_jio|gQx8dcs@SA0e42XhuFXj)9TuZqU}lBtaRKCbAB=xStj}$Pcgk7Fa#U0hvOFs9oMWSJ z5+(D%Jsk!-6!k#f0vMoMAQ5{-gD?uU7R(`nYqGN5SW<2He{#u^ABSTWUd)Uh4Ypw2 zM3qeZ^*Mkq)0nzTy}z`mC<&O}L0LBq4UIymv>iE{OpXt)pWT@c zBOy*Y;xjfLpGT>1bEM1njVf-?l}pQlne5%aBWuYzNQ# zg$4`G31TMcE-`2Jv+eW`DwQO~x*X9f?Yt5Z+x>lB`pu-ee8mXD%!CSK)z*T!1H?GH zrY6qC=Q?C7q^UIu-*!$(~;${+0{y5++jmMGG?)w;cG?{umbJxuE zN7e2nY^v6De)h={U{Mt*Q)i6jygPPdP6~+RFOvB5b8of%1&nxLG~@!NTl~(sHsJ(> zk%6i&T7z(5(P#(6SgOsHquVI;erJq6`97}1yML<;K-8H|iwjdkU@{RN7E}I@R(z=+9l@-QK$v!ndBEG#HQ{qHIlex&JDgbB_Je= z7!no@l5E&Jz?;gB(cI&d<57xU1sPp)a0d* z27d0!>Q3p*?E2IF0FYV%x}t_yr9A1~6Ju9Nzmxlk4N>}olA^V%k^qvK2eV(MA!}^q zFf3Y!g&%0JYA)*PQ9DnDVm8{e4?lYNN0o^rec*^Q<5AmB>W^}B8&;skqZ;k5bDNCA z^(c!!fq}0_KZ}k*?-Sfhz29y2B5i~0pSyh~+3LVfj4kF1(Gu`_{DZ7Yw^-+_#ZIC3 zt<%rNq9ETe&O-fy ziu-T7=a3j-MQKQy`*u%{z|;CF<5+nS<-=o5)U9i+nb&S4-C_IfQh(mj_dHaAKaXFU zRd0sV*+7k^04ZXy-R^M7bn%d*$!_ zCu~6K6cCeep(omgc;T#%Su&E-b=`f;;yU)j^EgM~0&t1vN$U+lTaCnLDw+5uh;fEs za<&vp_0)M2jIpc_-K@1}d9fS6oLGc*7lZsv^-&vaDF%POAvk&v(iHStbh2qGp&+bE%ydQ^KA{O{3+@ zukc+0LzTX~z+f?dTV61SIQZ{TE-{_#wWeL%+O>9gA=OZ82M_QTTyD;I5nS@yH+y6A7Dls|q@b zR;RjQ`{-pM|Lfsh)X2;Ck%E`g8-cx*qQxUiYn|4ssj6;*aJ^Y6t8`<1)@isEeF9E^w(; z?X*SFxwHxgdjl)gWBXInRhFmL%LU<60uc12amwGfKi@`0;d$Qp5C7%5-P^xV%n^-n`&ZQm5B;XzGDnX*#y2wA1Zhv_pDFFZOR?bBuYdIt zg}rE_4UO9b{WLj?nZdSxBtN?G5XsI2sMoFOcvAMIHcb-If$D|R)ZmUPSZv3fw14$s5m9^pfXVcQ{QJZec7zRC8je4(0<1ESX!=2)V zJ{3Czc_;7O?uU#d7UQ|FO0E$#U85SlXQRcn1j5@_ydeOaQj6 zdVZJtj>=;`OS!L%miK>>YF52Aucq8@T5cbVbsnEKbY9M}-V=QN3(j}{<{PAsJfYUm zlQmaY&F5DSAHyQND>ttp<^R&Pn+w?eStESe8q4rJP6BLm-G+t3O4mwvP$UkI{^NPx ziD3;a;U1gGcC{g2Q@wvwyY<|_{uJyZ2qO2`N$M%nT6VnbCw4hLT6A7&wx0tqASD!9 zZXgSrp9a=v(uRP~<@^o{)cV+>dyir{6IX|GCrgbrz~%oMEC8^Ex$>{>JHsCjU}rAZ zaOH>+h7{25zauYwy%curvKg-PAs4w0fbWPkJ;uPm;8=E;0ts1aii&=U!ynjV=JnRd zEkxvvunFPRAj?Q4x}+yAfUqc#Kzd491>-66o!ein7$gQ**3KUw17{C02jo8BdQPI# zv!2sD6u^t?(IIrp2*F3YUjLg9CUARqgWi-)$vRRg+@!G{E2MrI=l>153j##|K5YI` zZBOLW4ER3}T^}vZc|On2M&Pnr{(U!JZF;d2ky2i5u`u4kS~N>p=}X%3Wjg|Q_GF>1 zL@I%v(7W0WV8tP%0*kMyhC>wpWo{C*;d{N19_8lz7kmA&P8f8UoR78p4B3pM9xntQ z(bM;bU2C4NtzWKNZxSAd^o8T3Up zZyvk5UH-GvUtMW`PHER{`gcV5v?oT=7sY3Luq9T){}`O|9LO|vb{@{g$Z=40+d)uf z-FkS>p;iAE=%CIf3CkG72y%1 z7o*VcK)}ob$dl*=ZSrFA}e^m$7mUf86vX{)ua2{FVb9fJU1Z*)DU28 z+eG%hg`7ctC{P_YC6erx{xtq`wbaAF52O~KT5rzt*YHwW(PG|}wBN5Z*lq?P;WF7x zCw?Fii^Aq&N1@CKj=~lhREbs~Nl74|!GRWrWrkt4a3Mw$-B7!T$#~qlm?6YCfq=(U z_%G{$@0y!U&l{ql4cma5_*NeGYl~^dDYl=XHN(R!`6tWGi}pu+?!PN^o=j;~$tAAF zT5lbq=+jPG4%z2^n;Co%n_~C8zH9r+BO_)Q*=qelPnE&vba{G@c=+k(fpj+UtUS%O zl2*Od-&)({S(%KJg^R%CA4eRA5A_i#^&J)s(?NPr8Iblw z=%qrDq-UF^HS@GOfgLleV73I+)u8v-7O0C~7c!s=slpM!_%cog01$>0Tj6RH$>$?6k}Z2innuXp#Zu zDkyonQeB9juIIIv3+zb1Y>uus0b&H}vNH+LHE9G#-Z1;4gY=j-`UoB$Mi^-jNLCd? z5rvG;jZXgajm}OA;*E@{k;CJk=+O*;m!rIjllgX%n4XOH=>y*A|LwAZnhUHzg23SK zL-eNm>mx|;1oA_0Q{QZ9Bqhg-_(4FtTuOWqQkLA=wItIYiW`pepxAeXw@0&4=CFQr z8Ql74Z#)J-!>}QXc&Q|#^!eg3)_lqGSl?ryvy$1tZOe1vUiw_~?_X6g0XDPSRv}Y& zU&KeYh4{_A(7RBBSp)Q){9h5HCn~ z2EwV5zL5PpnQnYIUQXe#TF&3kH@(1$^)WsFN-H#` zB9bO@Xq!SYc*#5mTJg|L4DU$MbV$ZYXXwZ$;U31;sqGCLp;r8>n$BhVmOK`6OBGYN}W!&d4f3 z47+2v(WiF+d^QOc>P?<=Mpcmi>t}vfE&vicsW~&mlXjt=3ryF4(do4Dg8cBLB74p9 z1%+Y{u7jnWD!j);zAcI<=0rEwuHl9L0yN4zg~(Q#q8 zjha#$I%Ts{Y{R2d#*qyeZI&f<`FJyVc2Z^47j>%q`lh?md6QGwZ3Gh+Yyb_ynC3E; z{2&&gX%_Oc0_^Y=U!;q0nvxji?3l*t7M(3>tY69&(< zIzlJNdTOFsTu#>jD-|^hZ*Z2GU@6?W)LlO1X743;u%VYzo%N?=D>W6OpYU;`P{kct1lFpQZb?AAvObOy%n8Ci^Ir>U&G!G zopNttQ2$HQOw>T2tq1XgX=wdJ1gJb4N##OgThgwh>+|H>k%G&WrPs%G)a1?wkv<|>lOK6L68(bHOXCJ327eV5<^-jtBD_y#rYVN_%6e7l5 zkeytGeJqYvk!`IcIR!73(a@C<|FMK^LqeqLQSk>>hiw?Ga)%j7v0~>~_$`;a+_E@M zv$CX;L5jh|u?0tqI`^AVLzBa!ZsuEi3gCOhXa3&b;R{*x!^a;!LjW^u zq78Ymej;E=`TSAHMFKtea?zyZ)yFomGqiJMel9uQ8BVnWV_G88mW}*5AX}t8_yJ~oq2Z{V&-O3{DE3Yc*|ej6tnm) z;+>a&x;d;Z5doy1qNe6Ti5dfTcJ3IL8G1W;XuzksEu43&vCfVGFZanY;P(p=I&IFG zp3!F%?X=FQfhDHZuV!QQT%w zqZBRtBx<;2Tv<{YeTCH*j!l|Hc{G-E)CGgtb!0*4_Ln$OJcVrOn2z6%S>4PK2ujIR zhzW~Qshvk8m5OFrFbaFONS(>ILE^oU4a2q@=&8v4kzj38NBL*C&z0OQW+PI0%s?U+ zyAGzuNX#V!>>oSdD$0lWkpSyvQX;tu`R7*73p+ti zUD)QraW0sy?J=)w7EMuU*aYz`gV~l&G9kmsHW}>1W)YsNJtabQ; zHUBf)FqTN6=o31~Etm=L@<)-LzaYNVP=t6FXx4-v*D&Pm$##oBAvd7VUW>SE2UV7g zp;O-a!=uQRk{74siyjPmdC7;z>=_3*X?$?Jhu*BtSGh~kGfb6r)R6ukQ?I89JH#I< zPJ^YpfRrOUV=foy;c@AmyG|Ni`*mlUk8Nqe16PrF@0|!c?n70JvhKQ#G~ZVUH7 z1>3D4D&9%zkZ7?J_mg8*u!#R@%w;J1SG8APbC zH!))QqhM)4uHdhXU)boZZd|N%wI+Sq^RA=xGMFJ;q4bI@vRTn z8tv<0llG5ae(5?5SKYeyZsjQr) zNi7hfjDx8YI=#Qrnwh>1(f!@SpYhncuJ;VX>3k2vNi5@43$;H(560ED7}&W#67p-V zg&zHPs2~xFhXFWOa`zt*7G6!0U^^LhKFB6B)X}|T$VEl)^aIpoSxp)fF)AaWzY0kWn9mg12!knB>PK21Zmd_` zPa*PSWeY49q!F!O03(G`TYo;DYS^q&ivJ(u0-d8-lMxvUFjGU=j zO&h$ZQpg%$*9NmUZ_4}A7|1ff6fI#jEtW#EK;BLX~V=|cuq;H4@9G5LA-%ZXsX7IV9vfA2d}EE)#jix`MN z1QH(basZdR8BlAn0L?n9=_`)n^1kcYv|&i(zjqeF(}>1Luot8B@94bQ%oqoTw|)Du zRv+F~mUXJl_Lj%D>*e^*VO@fsUycts-5$4MjB=|^^1E(L8-?(VwCaOWzj?ZyEVNqo z6eC|Sd$+znZZ*HS$%1IyOSIeb3vw%rmK|h!`=nIq?}sC93wM#(tyzsml_Yexz71p% zec>N#Jnrnw#QPv-W@m#YV3l){)!^AkP2}ZORcDJ;XeiMUJ}X3jB`S4t?4)!%#i$>M zp=bvKM#CT0{k$@pWF3DSvTpPM7G{~{3%p#)0N&d*|9HcZG#>Y@{&fJ$cOiq)F#e@l zTP@%d{q(aR{PmcDEr96g>Vc38Ft)}IZI5FJ;Rg+F|99}3Akl5FhX@4akF`?>6#5n- zh}eN0LlsTn7LjRx9Eqix>Ww61ar;*o5|ba1!DzU2JFwX+>laSQ@zHOnYLh$ySHSjP z1Kehx99|ztsqP3k(?&edrW7ix(!oS(GW*sWWv(g;BppWoa^W@$GU@J98Ep^1!g>=N z^>4AwJ&Lr%%0?S0Ki?Go?lJ4aHAMci`YyFwF)KHWNCpvtptcDNqT~!^A424&E#7Y9 zZu;R$>jU^q<9JEvu^<{i)P`4H{l#3c+qcW{^jVD*b${_U1B@cNNW39)TyfyF4qfOR zEPQ@W@d2Qlcg8XZaZr@Ew0kCsN_6Gr6i63))fGQ|6ys-pxH|MF>^WZM0n=Km=~TQh zY}7J{YkPG~#!@MnaLkr}ZGLSZfKI5TohE)4_y0UucLxyBeUO5_gm_Rs4qu(|4r|8v z4`*stcjy-RpB-j)&H3*TJcgsnU*OhS@Tmhxvj-RqvLa8e|ZZ<#9;Ik-_J9yL_Xj zPv7a=uX;*+aM2m2yRsyJd_^8`hgEWVWrKs9{N@4N@O9=`7XIH96so`mlE&iSFZY|heWDx|S951ChPxZ=)4 zWH0hFG2d`wQ-3u`9WYG>#|kJ=>HtR#kK;iu<)jS9GAp2}z>wDhGNi%D1$0_#8PEGK zpz{7f;C2nNiHZ_>Tu&WaWTPH)ULr3rY>50H6qQ?jm3pTW?iO$@Q-CxjG5$a=2ygDU z^!2}1p$=FW3o2X1k@**{Ko7=iv`GJipS7ccg2@1e?@Ds+mqKBnX8im!KaNK2`2q+p z!AJ{iT$htl3_ySNbiHW(y5M5cs#k)Lbmd#{Z@lVapbvxUduWt+{}>Yg_Xq=Cb!}6K z{u){W6CX%kg9Ygw9=l}kJrIMLrd^j-)+C?m5tmj{Ta95=J~*KIEAnzfafMQ6ko6*N zM5?@4gUJp22^7Y5bb66Cm=^`Kq&{}sXCKxLTc%kn7oeP3I6ia4s=39kQ9gI+(Si_IP9IUL;shm}{wdhj?ip+7DsU_wxZaGMn zi~0UuCV#Z)`@+HyeEgEd_el(=ixc15)Ax`;3Idw_WgmJ1Q)fr+88Hef5QY^FA_AAk z^mPbxvKj;-^Bq{lOb`x+=AZs2BMo%((tLsPv9f^>NA|IMrNth%PHv7CNrvtKa1|Sy z+PI&ulRLF+7BrW(kb2D zf^>IxNT-yflypmqv~&s5@DS479Rkt~3P?%wP51uReqG1G4}OvNJ!4#BoFkw9Dg0TW z#J|V`it8&EZ8$N4cY*wB?aZL4r~Jsr#i6b-;As_j5|5?|mNR&BdZ1yb1Xn;)0Vqpg zDlEYCZ3C}*S(zkHtnBLq3q;vbBh;-gstX=}El@t-q>&@ykJdBEqpXhm^~GmCbW^dn zAK#$->|$_ly8OH4jDJQ|v^6SZD6(G|#6HB=)X~PPD{hmUJdg!};Xsf^qQ;rC{Z#ti z7LN#XXL$5Z1@xHl;vbqp3r;>bpmepMw5jg~3E6guq$khyoi z)*X2X5fK!LSJhZpwA+o^HDPkik>SM5pZ(VqgZ0Bj3bKy~2zm&bXbdh9X1Hr_3J}-- z-a&w)lLJjM#cII=I5*8sFJ;y9-nzW0%>TWwrjGmSU5NY)k5MpK=hzJDk%~4F`v=j= za&#-<^k6brMmsZ1_d#Rx;a{R~(#Vb})Gh;;P{OMAIjSgaqR%ci#rO18RR%zIEO> zCyz0#0fX4pq_X!$7y)=z4FAHa58N}}^Hvj$$O#+4fKn;Z+A)N{4;MF(gaR2#C^kb= z>syWLe>p%w>xLnKtQQEu>PJCdyV+z4Xo!4f#G~xH9`p=Uf)Hr!6>;FDR|`Z^xQsB0 zCX^$8;85mHSCSaelcQJ8s0V7>t&TriY6zoN>_YqNNrOalLPVJeVauL^c#+$)5q3=^ zIzU*an|s#!w%RZ3C`OXEBWZJ3%|#Yuyyfgt@sq)}o)7e>zXv8T-jr2dQV*Lepfkfv z?bAgFbZ92VY^{)zV`-+Ft?E$w5z(9S&Jtxeiu=FPnV|J>YFIIT3J5MXTJ#+2Pe}0k z=HGN^AN%{~u`=r7Dd!0ee2)6zWy4+8IJ`)=xbVuvcg=t$!GSWG>op0?&d06iD%)F0 zuUn^s<6pxhC%IN2+wAeRdjNui#&@_k1njTDOALOI@O&+JUP|ho5!AflTo-?$&`&+% z;0mUpC(T|A!=U9jm_P{*K=`AQ#bl&eQXRPoRWEMKK~-fhoS4~<|MfQ)bD>5r3P2}*YffsaQb@DiWHuORpwoQB&pR!c46uQ4(triiki^ue{a>-m{sB$KNN&Feb8qXMbwE+U;0-TH`$?P>qpEGDw8eBZXogfyB zEk(%Th~NTa#M?6hCqV_|*O!9RKO^gt0<-&_R$Auf=EOlu2hfX9GslWU4DQuc7{`(zJs#4XTh(NrWHeOOc7*u zV=t^8jR5=!-Ord;K|=*eKwW%im&*F*%pE|1t~@D z8~1ygQ=cSP6Nvjdh)NzCS3iZ^@3adH5aIE>vGS_dV~xd;FUwJ7V8UV`Mj9@M;E$8} z%8QN7Za;T(PCuV{(>!E41B;iJ7mSu9t&NSzbmq1r^v)ecE?Tn8(udqpiu4=->n1N> z4mx(Emjav|rmlVz>_6VQy)E4h>rAR9$2zPhVfZ-ocTfo6btUd*8na1vAC9CQrCd4{bgxjHO6+1ln3Vz~l? zv-^xPzckfzrtY(RWOphDX+cKan9XeQ4Yp?#=UyFm#%!qt&1&jue`K)JhA2OCiAzgM z1Ca?BKYh$t)0OymW(DG`2s50BU!3Da!Glg9keKv1yZKsKE+l7Z*f$kXB`p74@<>K- z`V>24lPEWG;;5=4j$FmVgZDy&=LXIL2YtE3Zm`7f=IL ziL7%kT}P9tXuvo_af*cb2buuNhEJt}N^AVV14{Bi@F0v@%yJ4p<(vIFiw|*01`4mKD z3AlxWoOV{$$;i8}e#Ps@2(Z%PyXepLU`hPKh07t zvXIdcFrdo+Uy&(nSHq4yd|+}K5+rQ$cCw%7`Y@~r_1=@A*p5p2Gaj=J8VR2RINz3r zlLGMnw^$(fx`^WxYAs1M8>mHCQZhn3Q&Li(b~A_qwP>l?1nQF;h6tb*;@lQzOcRv# z3^P~3$NY3!s?U>-0q6RMd2l$B?5o)t_~8SBRG{JD>G1)$t0zI2cJ1-1(QC!yugpKG z*2pTTlkOG0v<4Co<=vGKqVzHAw*v>i>HwWFT{0Z4i#uUr?+dZj=DcLyiv&azHsgs# z()gSnGM!3r7zJiUef`BR1rg)mCVG1f#;0+gMLdE>-oE#~+KwWjvK)%ql2!dIgz+$2 zQ^0Yuz0tGtX>Y0V|yU5w?dbeK=iFXMIZX zJl9qo>>*4{Oq1|2&MzwoE)?+EU8g1%&?v}okNQH(b-zxA=Ymz};&3iz95TG)s%M`@ z&>x7T6>)D_~2AN65xnIl+f&bQt>pxu} zkj20Tkdg#&50i?ozi36@`!yZae=2Ti@#zXi=y<%WIyyY8E-Xbvd2_eJA^r|o4MpN} zaycwII<*<%heEYk;Klf>Ju|pVptD3AuTGMQjg1Q$Q4y*>WAXr*H$;#%dDuyP3Vh`| zK-LISst;+pf3k(0oZk-o#rXK&y8HM5VOFK&GioaRe-6hSfS`$0eJnUbRr6rr+GJK~k(;pv$NBs4jJ%2Bo(Z zO^{lg06-*8!-|==l6Y$RlSti?=W(meA1Iiup6$d zAW%G$oP5O4`VK~9z=2)su*gM>@F)s4Cpc~tMde}M;M-?~1SQq!A_b3&HaVbM1xY~M z{e!bso_AQ?ozC;66gisQUh^8THSf5l1FOz6aiy+wKL6=C&dIiNW% zEg2|j>fnu_dP2;|CbcOa-FUkg0xu>_rPZsuo9=-A?Eo$YIqx=+%W(X%f8GcC-g8h7 zTwaIM4QxCYOxD9+s|-^)yK!JKsC%>LIS2$gO5dw>@n!K_^u)jAZXy#m_aHR~evnH5 zc*fG-eXj8pqSJq_{J&@6aH&f$QcW< za*6EnX#xIy+V<(hs1A{gmph|gOCE=<&VQin!0yy)E5dkeeQu1m;TQ771mDc+%mUdx zQt{m`1l$6JwVQrHmjO*u=CWcvQ!Pp=_-X+}>bEs9we}7*esJRwC%Psije))D_U~X? zi7o`Jjq1uXJk){qh-7{-6dz5?XyntnfdB{2D(`%H{@ z!d&xzF~vxgT&Z^`gv|qf&k_o{@&3A9t;f=1S%h^DMjEnb-QiRcc^0wUKTA!>CT6(K zYHVth5kFcVv3>SBS%jMP+?r|tn26AXOL150-)B%-4fAw4FV`(PQ|NM56ZlyG_z%}z zT7BP&8FwmiB8dip4>$agn9oRLU+4oa);-kc#m~PTZGR*A{&$|UL5UYh&womZXtx(b z{e}1a*}luG(P1*e*I-MJA($bwx27R6nLcnXT5>UtaN*`=%Djc_6OSeSF`VVrAx!Cf z$MQi!}VPXW4DBwz02R9+N5lxfJLVEUIMKDe-3`S=~W>4tU>Xef2)bf@9&Nra5zfD zpv}Fb6z6k8Y;x;~tZDv%9D@7yT%B5b%&1&)VEoR@aaj)u^7~I+f`%!o=Fa&y>~}K| z)|ase*{traZyCR8>gYgSHo6aHN@kX27$gw3CB^;s>+j$MbOkT)rV8CYsAT#xn8P+^ zefUyY=`>e{ceb|t|~C>9a?4an?)`^ ztLxGkYgJiZ<1+Um`(hb?#_85=3x1hGCOYkRH}>oVyhqIGd+x{IcP7VK0m;MahdgM% zcFpd14PoeoI!dbp(^HnG@bw_B52!F2_`7z2Su>hcrAV$74T49vl13%jwi`8VACK}< z5YU#)!p#T)l>odv5H59v@I-B`CqvW^pcC-&@7G}PKh+EjY*(8WVGS+#dGDXNGidS;;h$jLv=| zxwVad{`O+AtwJ7xw7OShJlU!4h{z-@T2{8}DlqH~6yU!Xz?l-&Ew0Zh%rN4Y#gd>j zuew{ChVaSndZ?D=cJn2R>|eUM(A9Sfal98j`7ZK$H-?0_$dt*XZF?6EJF27 zOyY;8D~r0v8^rSH(_JMV!Uw}7sh>@b<$sD$Vg6_N2tMkkg%v}fCxU;$e9zys7FE(ti(I zbL+PDO7MI$BE-S4%#gXt25%EgLPT4QGz3==D+dSFm?#R-oCym-wct5cr5uAP+82<* z1zh4%y=E)}&mXW#LB%14Av~W|0L<2HJWHTUgm?|AywJd^S@Nefn0^6dq4}f8aem!g zye9mG1^``8GJYU3MiKZ0VW#aX?|%UU*si&q=~scl`||6yd?-)3*rd$>`TX$HMifxQ z1_;=)p1xcBoy+CRc6bP(d6PngTTg3b$=@Fi4cuKFyKN3mf%eZdRhGb2xNh1%g(Ph= zbf)d4l6!;SyZAFFQ2-Y~;t|Z`|1x;q1D33NRAp{wkSxNi6vmE;G24{Q$L2|^G&{s+ zm^0-v0Oq^Y>_(3pot>)+dwaR3FwhrTii}UhkK`3P-k1$QdbL&~3Gqs0E;X6Hc+HdV zQ22~tYf<{Q;wEt?ku`>qU-q;U+{wVx?Pn)!(lA{Yv0}U5>SI` zH@LZ%eXBDBJn5**3GEjV1mymf&$lPL9!gYRL@c^UZJ565^2+5%o#dqz9;I1N*mD)Q z-i9cvX=-a*P78`P19A>9b;V>fv9Po6tvVLikT7ZaDCbc|KWkBo5C*QFk$U!GC=?2i zB@EpxrNq_|ie-MEkrz-x8@HG#H6=4SU;Q2a15!KgM!v--m^n&@Jp(aRq#ajq)}Q_n zqp86`Cgd@>P+`db?sO8|C@Dzlc$B1PXeS0FS(dY%mNLu^S{x6c1^Up9rZPLC6nOx< zajb+JM!tHV_slTD{2SJGTB+rmjJV;9owSr+1(G8*-Z(R^jsqU=^iWc}7*g=-b3L)j zhM2ebH*dZ5hvmJukLdW7rYvveh_VGUTwe}U8BzUrS-~=5W!poec=rY&0v-6ip^4qz z=l)*6Ko6$wf_~TQ8O*5uqh(-I}*^E zDRRnw&x?~~D5k-^Yco}Tr@Z!=%W4`(y2JF5f}?KfaKD3p3Et0DQKTxmz+*j$`oDPb zfbhU$5LPcoZ1M{YAN8p*(N3_Q9_eZC1}pQo9`?j3%W7P_KGtSyL(qD+MddI2fDd2Q zo?S&WhV%cY0m629Y@A^Ru|XP@lW4THH~JW(v7@v=0Xk)kGg+c-eQ{+aw6?ajy}iA- znC_DTEjF^xLey1jCvCWwa-1!gzrrw3@#FE1t-Ijwt4-o1gW(?V$tb6DA&}*B&BHjc z+D}6%@~76?p>a;84%`p?L-ZC2Ave3{-b$9hMURTl(rMKM3kpXUawZYE8m{M!;8B>f zOf1Wk4uG2!5L`l302$&oNCGcf+`8^YYVt@Id#Q5_iJVDc3c0A&+)YhgF9_FOg(9N! zLBWPt?R&BEH^4~C@%lM=nfm5HG! z)2G>ri$@RFAIK-s+{(>>yW0E9a+3<9W^B9!O|WGNGDwqM;Pk7Nde^wd<~}mIK-!|P zU&As$WcYY^Oi|RLk3v({7)%i2%!QVR*TX2b`@w`rSLc=8POb#=W4$h^e5R*-#I zweEI#IO)FMNoovseCOhO6=R*3vB4dMaU)r(nD!Hu{r+)mVU5c}a=7r5kINADD`Y{1k#LKiac74hmT28;E>Psv~+$nL1ub;?7uP zqv=W)G<;)3m!qPmF`u09Q3|t^VsBC?Z+n^ti%Cq^TUTBnGdLJ`444q~4Ko!NB#oB8 z9i(<3FOUcDFeu3w_1*pZM^tpvw*gcxw%owT$|E(jp25BIu+!)hzw*!kmQ3F?mqN2T zR8D0QK|d9)?;Jb}!T}Q8(~~d=qL>WqdOO3DhKB9k7l<<$7Mw6B$_fYUT36w9ZRtqB93{U8w-5wAJMQ) zy%`aVjA=;Gr%IbcU~k<$oGVY1Bo8SOOvQ|Kr3~9-5Apk;lG1tRA~+cFVsuv#o_iO! zlEh5{L98SbiEL)b^15*YJotYeqyG?cYd4Q`YA96B4@wJH1f}a2m-zGc)uU-@phZ&) zxCVdGx0b6+s-Hu&rb*AXbj`9GlpU)tp&|RyHQJw^ZlN-+X(6;WylDT~W+$)7wRx24=w@d9A&HK=O%Zma_^Cr!Vw_gU<=7oj zCTR?w$yBg4V}c)k12%m-If>gjgU&yTq-Z+=^ke>o=>q>wSRAQ+-vE*i#^n)Wj*KZc z0!1$31~v|MYfP~?GR7Mw2NMaLTk{8iP!RRiC71$$(2FWk3pNukmD#E2=mPPlibT zWDXiByloD!`!5cMahihEf;D}A-YzLD56;hLCDzUzwA7^#+m*7qZ6{|$@@nOL)a0bq zEw?O7SEsir_##zQWTQn(;zx_dzcS@6A48sUzT>U^v8etd^N==~Qm?{U%Q21OF9$m& zjgN0iUFYE4SPWyhtnC+D+*^$%`WlpY*3=m3nT;Dn7Szgy5|=Wt88tH}v}_f~tldu9 zDpazB?tz#yWyI$8{6DQp%*CTw3KIEkEp}W$Isk=}!d30pdc850R6x_hZAGv%?3pR0 zpX= zxcfZ!DVRP&Hp%VZ$AlM1LmEQ`A*n@&Tj;Wr-3-*fZW`)%H(#*~1w{f1B=1j^UKN5@ z$7Z_TZU)>3bHFJPr0q0;;R*N+ht4~G2;uTL>nzp3S#DM9=^ugqT23_W1(_CKfRd^v zkBMS56^wObl$y8o>G@WoO|e;F(q)m~OhVEJot!$Q25pzg31B6- zk$XREBR{4#CEt2QV^KEY@Yn;TYX6K%rTlX=YKuIey8h?rL8=M3(l$_BSq{3wWw%oh z)4uTuq3H{WpL*4vsYqXbirTVwJP)3bg({Qy7u^FNl3gNFY)ejl5Rt}x9e#V6O4lEXJ-#%HK2m)%@ag&mkv|~X0HcF8EX zJO~SyWbw^$13k2Zq1rMoDOVu0y##g4oLSMTH&Wo=)2nZrYB(c4uM9xZLBrRuj`)bZ^P^BMzn7CTn#?br8E$5$@p{FV3H zm=nV1hcWD%Z&Q+6-1F-zuh7mb4B!88?g)6a+s&YkKqKXM)f3Y2xjsFXlRg;6Ts==e z-x&6K{eFuU=U377H|O^IYm1m+=5diz)3r>e+rBumWswI5y!Yset3JP`!cZNQqjf%e zA0J4o3O%~Cwqtm018l>k8*q5z70 z01VYMmD4bq>(#(dmO5@f{^*YuPT9?j1Ipa=dCQ#r#6~6A@*XUio((V0-HKCmwjBhG zk6xjt=yd62>2q3Z-+I!zZH_vg-dK&!C0P4Cd)Q7Z;gWTo^C?u)B@%4>I4aLs+++}~ zk6mY(2Fq>4{cDlYDLzrgZDQE6miOBKt4X8v_|J4-&tD?0`@pO<^1^}hZn|nq;Uzhw z*73zcW~1pN2pI=OcpiU^vXI3<_2D}?cAbcc7xnog`ec4k+xR$=^Uw%_@G~1;9qBw? zj{!F`Pwh4*aVGcsB~`5ck5!Ln0U!_g<)m~-j`9411Jmup$&>R%2(PH;?YnPm#@X4z zyF>KZ59U3^t1G@=eJ;~=91Xl2mXCviBt4zb0P52motzA+O;^p=ez~KFPc$O_wl?b>|I9?(XZIFP&ZR6nxw<5B5HH%wz(tmOvDRIxF3>kPrNX{^Ad@3 zyH{De-Oth~OpK13TQ*~x@idR8(7Rj`;KAoK@2wVavvF8yaa!E;?h@O&{_Uj{U_L$K z&QEn_qigj0EM}tZcJsWL>ES|FlqQAG>-Zk|k1&Vl1&2Avvj4T;`2{NImX zBX|y6sBq(sm}Ll)r&&9BVlGZg;fgGgvJ-ZF*wHGCEYBIT0vHt(87|pbfLWq(jjy_S zNY=O05awCbB>iizmo1gk)mg_3QZp0TD6@#T&SP;7?U>)41MWz4+Q0xwOPegN?F3vLxDD(*3?RWc1|(H{x_i zPFaY^HF0dfhGLR5W6hZxoozgGPW3oK(bCbGHYYR2&JRV(Na8|o$*HHd=4Cr<5_-!k zF?x6?ITwM!GdJ6I+KsXq$uq@k%xfXXd5wocflt3n?S4WcOAF1Gzee6@a<5?I!=V)` zw0^a)zkM$iy{zzIgnuaxYIPo-NFA>A`K6Ab8}{J@ek~_VX;E8p{<*+QR!F+wN`?1X zvYi$HsB4gM`kht<+?1>E8}a)dlgb^OZLM7#&OeYmoi@G|;oM5;fNCFZ3 z0?F-T;gndn)Nt0<(>Z&;D$Oap8k5e{sUUJ0M|0qf$s6B{QYBu=)Kuc zk~Dey-TbyJIU3LW;{({T{4&ncO5){o4vEZ$hU`=5Y z%F3$RZqKq7xe3_PzykdPbPurUaX1f6Bb@I}1kboF-6JRh5T6MeU%=|-%{c*=VYTV= z?>8!nI5DE1Zu`)rOSErWn`72JQFqy43jSPvxTWuze?PlF z+i`BYC=szTu55eEc!eF>l4?)`JU9l_0-?RCB1@sFDck#E=~2P3B}*#J&B4d2;uvphJ|C1^ zpb_NDkr&(f=6;}I5~0|M1uy=8KbF$x6+0=e5Z`4#^^4v=WDw}G>qFgd&UVUmgd6=9 z>lE>;^qS2tYj>C@?3i^}4BJV$*b$yN>_pR=mg}%tVSJC?iRn^!(9yY%5~xt=3YzXz z;AS_0+V;6NA^fN`bs7F(Woz|wDY(MOwOAOnLhqqF z-Ii+s)5~)}r&TW2%=X+^s@%eAiN(NCUSg@W@?y_=2Y$gB=dMgFyDFgD9G>4ERk0hl zxPx$9xlTne+W(nAUz@$jQXhZSF0EaKzK~uR| z4H4N$ee=*rGu;o}Q_rc`ayW{RJhWl;wcM`F2`;3z|JQ7djXL&t{$>#4a=Q zn{G2&=!bzqlfgK)rUKt4gPn^OW2O?KqcXOunXSh{t&_(k1BCK_wz7I|cVs>FZU|cZ zJE8Y?xn>R%HjP+a`22Ri@9WUs3zXEPlbOtpd(C;zwEc4Ei1x0ibG%(-St4CtmP(g7 zNs$qK^NTgB`Evg8?m!BAphkJ{m$BOS$cW1qzpD6Pj-{FR98(-JS{hf-v@$=C@K=KN z7C=ab3jm5E=y7WExdf(xDe-Km0)Xt z&B?0qMqV5#@6{#$>-)@($Cz~0Q8KST!T?4ts?mIs4gfrBT&CBKa92NpO>JNml&lEz zI7vzO4Y(9M-R=cs@t+^W&sW65XI4IJu}4<0=nDk^-y){yJ;N;9Cyc%3gd-V`ilKxf z(ka(8E)37c`M!osUb@pMFK<&2>wy4ya71gtT-VtJO-cDCp* z#pk~xGbW;qhy_97nZkfBCDMjAgeLBJjl2?qz!;4NcTVZT^*g>578f(=$Tu1q_}wjp zyt8Rvq_w-MQ1kr0&LRn`EPc_$;X9H4OQb4swd82H2sIEwv_`>BQXa+wcLItaJ;C#0+l-yiV~TPU+u>S#%)L70B^ zVFD}AR@H|%;g>%W+8L>ckLCBsjfB!-e<&fpv>L~WA%6vQ~3F!FKIsWWL+X=KQr2hk%a%8jn+~%u+xSKKd z0M6spSKzM2Os|~%2*OV}f-QR)M~~NQx{u~_(_!T~CN)17=fIx#YkmijEr$$%P|-gR z%fvUM9%jhN@-Wk%DksgF9*sdsU7MPi3zf2?DJByD?MY%o2@=fIRSs|L-(?LU%250| zq*($Gm4>}^Cz zC7UkVg5(waFPV&yYXoCanYmKsq#J%P$|4tQM8QwUM{VT?#5?WH2&sk|$X+NmTgzYA zXf~XdbYj6l5BUq_xLl2J{Gr6NO)`c^gnpe`-F)P`Ga?HLIKnv!T0N0R2J;o|?f3{3 z|Fdr;TdUO{o1S|9HALLq$3OHI}13tFyr z0wf%LX&IPb{ANA02j(M*OWtTt$M)vKaHpP2_Ns(3@JQ|5R}w;-0vW?{W4}t*+qn`d z5x_DnD19hQb{g(gmDm?_-(Ocgnm;c5)}J=XGZP%geDOL{8rnw^qZvaEX2NWf3cvrr z3WL*}%Swk%>BNYvs1#(Oz({COtfagl(UL_YrQE;oT0MqGqFZ5vp(3=&uNCxC@KW+I zp<{d+fsF+98L?eUk)qMO7Hx|K2B$aM$0CyX$uF;KlN*)#w4@f29+c+}v~Zbv+ytT~ z_7&!5sf)1@>$Xc7rPoVi%)(GR!vLI!RM`5Rv9YAbC8KR(9(UfD_1HzmeY5Gq%(S9b zT?oO<-92eKm&fg2uZEpO+GENy+4^(* zcYBTDu*meiEfGaIJq?ZV@W{S5H)(8z$$mdl?0MOgL_6tr+}mMPc9aS#hOFCHw=(rh@;D4ZP{O=TQVkcw_@K)Ebt8ChBJA-lvY zC7*|-5Rb!V?ER8wtP~flSy}&$(a>No@$kqk8i@$;=8cjRO{F-&!uPnM!d34h0pqzB z-uvprR^Q@iLh+&ye~~cr{ak7mUQuiCA2ICpq>eH`>h%ylVz8s?Jt*$BM`Kb#V4>|^ zf|Vgtg>dDuVk>iA2X^yI&V{29)q806WjlUDGY%_*#z1DHb?h-Z3 z6wMZO%kO_lC?bqw1@3ze;mVoLB!8tI*r%IJpxMiaN~?u$Ify@Eg|CAb4C>plz#yP? z?Z(@}$e}P;{p8xKjg-hkEAQ|EF_Wo3T}ti)jgW$F6gD@4#T+)Z3ypnuA~m9_Ec;ZF zfe1w8B?oR)QgsugCaG7>*62I_N1nR<#=e(7$POWhpMnf~zGP#5>oo!qP%;ZyFsCCG zeT^{2fiB3`9t!C-ZG)F>p_}32)-O{9qxqeNh1Ws4Vng(foCp=gnN&md7|<+;#joH$ z&q;TC?)K#_=y}QWeJ{io?1*OWzKUEUwj#L>Cvr zc?KQBXrc^LUnaGc0ouChJH=x#u_3fC;dDo}%>~drYLH)a_WmNpp<#uP^ z_1P%q$NT68JQ9J|eD(Dyg2zq$%T<*vm-9iwzFs4RlyvlNE*6eF<`(`YQ39sU`(sX>fQE+#qwYt>yRrXJ1CSPSL`tMk;H3y9O=ZbuF zlt?FJv<|2+g7HH*fEJ`&z?h9DFiA9F5|l0=r!MMosO19kJF;jhhB{}OwqN>*U=t*5(GOA-_};Q)yuaR0r0faD zPL(6-7@=i_Vc9*EPmNzR`hg3c66>?Kf|PPf49(T$);yHfE}XPDu7&X0{6evCe(Z*` zNt@CknjB?mNSwt^xab<4xk{QJ7VUo|n6J9l@obm4{pfwE84zuaV#Z2N(fgy`t_M6y z^Za~T+Mh#j4e36oUrHn7>P#T1Z2bPxQf!ZZ&*5=+A=-A0{kFn^3!<#U1;dJ?mIT9# z`W`9HM}(>5f#>=A;V?a_v@DF^6MeCX9>&kl=!-Ju9I%EHxx|HWU`$4N2Ai#^N#>^2 z2bF9vtsYDjphRrRbtS2OK4EnYIb)N4Es2x+<<*BUY<_X(?~X3PyqY}Jxn#FpuTL`jL9QI3WcHi0=bQi`BZj3XU*RAV>gu>I(+3xzam=kbi$8PM9X%a;w!Qvh zP5_>wl(A4BaV6;P`uEl|s&r91$u63X>kH*^B1WiTi%}!R)<8oGSGAzmYxb55`5@vty`fFN8kdZ4DE( zY}BU(yY4$=Xf()0lYs9sFvstp=QZ&CIj={{^g~^>lu?NMy0~di+4k7L{mZ zF(ukO@#}P0EMs-rsX3k~IblklJ+=j2o_RD9!PCcqk{gwukL4Xr=2M$R)W?L4k1MI~ z-|l!(PJb0L5`Hr*{B#}gIMLk~6CVGf&b#NEu%I#K+YN2qhnG`&yt&RM5#RIFKKJ%f zKGLfkMyx%|=NdY-TpcnG#F9NtMNGt#a=}t90wa*u8Z^FG^?gFxw%60SVbguaQ?vEz zSG$h3!#k{Y5#X41?zp`@qX%Zi)`#oI5Z#9>-<|=qI%CNpI;2#Wrs{GTB~FNA&GyVM zbHoY@YkBd{&M5*dGpBzajPmcTUdyjFOLEkmO<+nd;)*=fwDG`6yNwUzK?U#dKc$uz zOlstC!&98h-u2N=wk5cNO%7Dp^h}t~t8RZ_EpczbXOy=T#EYyDI@>*&#FN)5Hgj{^ z{=i7LUrR?jmcmlQC?Ta_ctdE+NJrq>J+)h)_3(UaHNOPD?T2Pe^n3B+-!zmdYuj6+ zX`%3V9HFJd2u9|hY18xPN`d{+QJLzz*n@J&dnZ)VdBLh76SOKxqj$lr4BO}W43@_~ z7J*dzy6raX1{I%Gzr}SU-wMF`Olr*MHv7OQhA@cEL|GD4-;-!B7OX~el3yyW>)qo8 z;$DXl1NZwo#<7vJK-nsbbi)syeGnUs&=5M`{+^b;D8m~eVAXH+JE#TO@16!`U6F6* zebI9hqP~~TRnga(Y7mM4QHJbHz!tBxz=$M9>cs{Uqqu?J@dcC~7*WJ3!Ly{u+vBld zbbeHPyA{{>u!yJI`Y`^cG3DWR+^ZL9+%_1Lw?Ct{TK+WsW`8_%E(i)2eLVcjnfrXv zi<9vFmvaE}+YQzCXEVD6vK@CJIYN!M-V&BApXw#~D*T^F)~=r-m3eq?J1VbHP23&^ zpZ5rQv|k+_JtchLe*`O$HIS!tP5jY~Ue~H6{kc6EjCk}<@* zky)rI#W1sY2tODUw|W9Q<2q6>gy{QMauLLaW8(UK?Uhb98~8% ziFmoFo>ZAlD--@*U-~1uBuBk_UDY_j2R#f`IXe8a9%Bkzp2E370DcBWDr=rHfyBq zIo3+ISL#Ybm&==CfuoBqW8vI-bM`0TG$*d0R3ih)E~(_(p1tefX}i7F^|fvk1YYC~ zwJ~h}O_3kY|JibMzphP+rv!RW@w9ydS243et_$vm&<+YDU1JYCOEuguM+9_SFC-?x zUvi2j4fuh!Es{zr=CG3hjMOF^_`$DCW{6L*Ik5NHI>TND9$T(eMv?o+6wFTbu_0bs< zk-2e+P%c7(s=&qiSQ9zOSvFJH z4a-!a$Bgig`@m=`?}6QX3an z6iDbeg+D-C4RzX5j>UtM%}`MFxjZmli!_0e{C}&44T>*hYVaou43c?6K4d#`4=*s1 zu$WGI)QDR>f7KzELmlU|eEbG|E^jS%*FKUl4WIM;eNvT2eRBRw*fptMtQwl3u>duK2BcC&WWfGTFAXG5Gsnhs6RZ}~`X7YP?`3=AoHDqPRBno|QH!FRvjY4c5 z_#SoiO)B-wL{JbO0)=65g(+^5yri6$S=d~=%(XbsNz%>v0 zq(V)2l|!VKmp8ugh;B3DPqWqkXrmy{qJ%W}PAW;xI4%nTSXUcPzv1LWvBVArNqDh# z0y$|$om4FYZl8OY`O!Z9s0#~?q*F#kLR}g<#;k+=S9ojMqPjQ2u_3P3CsR4CBV)i! z8`o|jW$aH4MBkRUUmsq*S6^SOSu`wm?3#hMIh_3hrgzxx$fA5MW={LD4c6_ex z{I(p_1R68%g-K>-V7N)lqb_(z3h#t9*G_gY#Zg8u7cbnovwTz_A0AMwErVG_R#29> zGb(Mm4FoNB^I3Q5xV~KU(+FrH#mMI*V>~xxJ@NB5W<7@a@9;TpBp#xLi8O>3A@hBG zudex6_*QIGaR*Pt78OP@Ka&AgvqPbzwCSS8~gD8arKr_ zS#90>@J)Ajmvn=4mq?d%NrQAscPj`g-5}j{)7{a65ff{PTk}<{y-lKP9-YTpVWP!%IucLArOMNA8h3}g@T24x6?LkhL+3%%`Ur`@DVI+{qN~XW`uJNW z!_M43c#Awt`~VacA{M9LLtIQ!_^$AVL;diWQc*?&VXhYYa7VLtA8H7%u@5<|fl+N& zXjJ0s_V1j&A8CT>a=KZQv--*q{mJibzZ8PDE0hvFBEQ#>xrrrILN_i=NY%!Bc^E5; zy=3{p{ilICNUR_by_VZR|0&i!2%V`%BgYfq#vFyV=pAEsodbQ)2c~Y+DVp9VWMSi9 z$Y(<+h|@Oi-GF+h3Dr?VGvM&PfB_E&&UHa`#UQc3E3Du=)eKNIi~!ecSLeT zMiP;x;x|8kvqc5RSkh={^b^+r3-=toU)=zmmPN&dUYIn>hq-^-uBTX(dDsOZ1Sngvl`bw zp#(voOW_GBk$;XvyPwl@R$zz=Gz7She;kM{@L^L8Fd@--OZ4NV+Vo&Yfm80R=}fqQ zYo&i+NU3__gF8|!Kd3}0i#x9lZww$VeuyUUECS~hJz_r_Cv|h&AW?%ne#`?a3*dgu zh*pSr>^t>$gr?C86(DqUx(K_0N^{O4-pqFh$dqRy}`_R+ji zl8fRuH6^Rd$tnsZ7dw&5?;84ECcY9!7aus3=h649h*6!DmaDQcf!fBAr&!%Q1E_1v7&t&BH&fzWH5w#rcT1L%TnF`{H6~^5jGn@SA8Uj z4ZHtkdA%g>f*N>7z-o^Po$`c!b$g0H==2c}Gdek>7`t^r;}?1~A;ZfT-joK=*I-1p zAfifpk%@R*VJ3C9>_77C?fEa=we~~~M~#3NTAc~n3)oWtK{PU33wH&h*O9o3t=-~q z46w7IRZ2%zWREoyu((;QAKlwPh6qOy@5}1daRF#Oz(Qk5;7s1QwC&0NinskVCCs&A zY5Ht)mjvb!I?CZ1cu&6&D02WStb%m&wjP9bN+1F*T=@Dd$#q?U64m6u>F=^)3ORmM z-U>^f4uWPSS&2tl&D1D2O~H@zN|aa=JL3vg!~S0p;iu8$wcC&o2P19N7bjoKsa{wf z_ZsYUb30XjV8&y=x@P?BNmF9+@_`$YUMgrRa7Vlpxs1bEMMy~m#e9!G)>pHwed^&K{ zGs6rLXPwB1f9jAVxS7e=?$L`Ww!`q%MQ1MZ>=6>Tc(dtr;;Dx?bQxgoUtZVVUhmEs`y3qxoqQ2(_4ckDLP6cI z6r~9gOhKa{eju@ZJWC=%4!UdtJqU~qp2?)1dTA&G)4?tF5HI`^)WH=B=qQZ05A`fJ zZ1det%&NU{?Gf^qPqTye5rZc0@^7umlTUj!7Xu;UK$F|IF%&>m8^=Y?G<}?P>g&8F z`|q;yYSu@K>G31 zT}Hrxu!+c$TPNVsRlKZPkYv*&jYBVVCUA{IA?1fo|M|PTZU;SWZ}UV_aRvpBON`*!91wKfY|D!eao>O`M+^txLku_tig7tLr0zmQoEImw#O=;x@rHpU zrT7x&X-?*W?hlBsApXJ%?;0)pHFYA_zbsgR+GZf@mP0pUymVY{I7WhjEI7duP~n)I zk*ceG1*D7E@5h4K#g|;fU@{M;WMRm^*%|Jz9)DI(h3MW+qIz#M@nQJ;LUno)-&fohBiLt-r=*7%HX`3-A&b*t zi}v&YRg1B-JLGqxp@Poltf^d22y`a|bT+4)%DJ5OJV-?#ZVTL&LxQs+K4(kwdYI?6 zA*BGn4cf<}67j)>3S{8_3?mZT;M`-mUl{Xsyk5_;O#y?qNJSm2b6`}kog0>c+>*(I z8Z+&pB5}lRqp3ixSJYtu^>dIJ!mKc%jh-S9Wx=SPkwhL>Xw4107G-(G%)cUtr7XiY z=Q67LMi1>8jVKCOy3}u##n#?%0lbkZ|6S#qJxBympuDN&Dfn!~C1e#xLMjhnR)p6b z#cPhKy6#dgXhSN?k^J}M53dmNw}e(q@b_2(b+8jCEl>YofuQQX&enuqAXm+QGbj_4 z94z5-K}b}53W#YsC9QKjk@fMo9_zEF>!(o)&_s{|6Mx$vpWE|#OcoVI#&3I<9=ezM6iF#KO=MlC2+RrJUh`$q z()X)|lpvg25C@9V_~RfX`gO?8e4|zSek#A~;ZgYug6gzjN-VaUWE-OjJ>nPRa53pT zOnKcDT2Md`B>F!Sua99Nh@vc6UZBeM;Y;IcJ({Iz%_OUS*)o+u=$&T6QP^N_(UFJW zHdlNKBM+={SU)MKi3Vt(##y1@P?t3-jl6nLar?j*$eEi_F2Jl3A~iBHQkv8Qnt1BT z?lBh@Q8WXE$lyOmJGo)(EMyD>1}}wo`q4}=8g!~B|7T}Y3B{kiWb##-G9lCc%n_p5(^YUj zvj~)*+e2iw=q3O=|J6U`$D38Ul(XQ~ao}NN`wJ7@N~-)1cN1%+1U6n{6I!q-SfZ{G zf;M9$?(P(IqyDFDRWLMkBMq5)Pd&A?E&FTI?#GDiT3M#_^f4w1H%05X6VB3^9VzR$ zQp?gRoRRo!ShARVbeCBm-X3hDCw&K+WPL;?&#ccvYqx{0Q1ah#Y5=@`#rd&`B zVCe53EcIB{fY5^Q>F;`+vBv0L0^3%Pk@u25}?QQRaM7{ zHo&2~u98Fe|K6A0|9xMYFNkX}L(Esx@?|ybnR=;uT*P z2qY&y_o#&zD2(Y*Snj0kG?RJ$ZM1D0pocGJffLAs*PMd@yodyO0I2CCBFW)nD2JRJ z(a3wHN+)6du0yDh-2YhzcpQInXbZ=@16=Na-OB~Qv8Z$G!z`Q_jbvh;=nT3vyx8~? z7d!lM2l+5}gE{qCkR8E$)AL!Blv~?{oD@QrXfu6Dh;W=CQk1c)zbL0KV1yk_A^;nm zO&4kegoVUoMGrSfW-ckFF@waA1R=tGXGoLzOy`vy{&-{I`0QgLEUMP{z>pVwV1zS3 zNy~v82V|#xt6YszASd}LH(LhF%2|)n*Uk!yFMQy+NlwJC1uo1-v&w#m{9#;hi(Sb75Qw0yXu$^8PQL_deWxYZ5_odV_t`;6q87R; zplnY6Q(}PL%#o>$ClBIKpzBTs=mtcaf>Kmo@k#5CZ z$Z0-Hk1c)aPBu6>ls_TvYk9c*lCPUByX2PbdOSe@Tj?hpzSJ6pYsZ zNbLf6${-HpF4yl?QyJbjJDTJ&{UJ<35HF&Vj~bq8zKUayR(!TljJHrixpM8l6X~Uy zsdLS{@`&q5N}HRTv%rY|88+YUT#bMp9}z@PgrVR`5s9-L>c3ef2a^8{S&MEgOJEQ_ z?oTJ@O~6h7gP?DW^7pX(S+0@E_pz)Wv6Naa28&}TcOc^lE8n#-J`zvZ%XHu}-YoS; zE|~`awbs84lLO2Dr}EyVoe<%n(k3R`f0#Mn5Jhv}M&6vjZg~D6nrru15s!-=yuaQ@ z4leqDMGGkbl}Bg1W&3 zp?i~uMzu*eu4E30h76!60=YIw4Rs&HWM?B7@}N_QZTXbfcT3PZ15-YJ;m5SbnPxcn zr8v&(Lbi@9>1jSlA0sfSP2qjB|9sc`7=~^9`|Fv8p&w*||Nn@QtN;p|%})Bww+(gn z6f(wkD$TIH0Z?56q`vh}*i!m)3rYs@ka*>&cyUU2{?VjDo~;0q(BS?k*Mv_*D%C~@ zDm83-r9=?Tvn7DlhvRmAteG?Pf=L(7xMeh!9C%VF>)1|qX>I5c%(zPUQ5rurCCsD1X+SL=!Ur?-;F^`D6+vbK>iFIz&-C2P{h(PzN)OQievQ6_n32 z^oKxMy%2O11GW2sTv&THpnYEmh5gFZLSHkT1&L<+-Y43}{&>wD@xN=s`S(85jE@t@ zO(@*>o_ovb;Z(@WKgSC)W(3YElMzNAzImtJ?28N4Qnr{$l317PDC-X7xHd2?TdguW zS+(_Y#mTlKjGywqWt3x+AB50S$qOqxiWzm(1F`}NK5PQ$yfhe2+3Hmeul!08ihf*m z*4tqpvw}h*{Ahd1SI&yNhbGP5>33%DLxCVI^ip3ph!{l}v`c3sfjloejJ_GHbu{P0 zahl$Ac=V-6kvIq}%4n0K=x%iTK!yW2<6d#0InXGk@&ddez*BaA1PFHuySPm`YrUG? zs{>rG_W(Vc`(9cd5Oujyx1qz^{6h1`|7s)btPMzoKF7izf(3~AuSu}E9kmXoJKon5 zaBEMielUP&2j@s+p~<$~OjJZ49|C^UNCO@u3=*pX zc%{;S3#EH{m?Urjl;Zm7A7bnH=52T2wgQwwcR(1_%|XqwX-T0OQi%s-U6o3%Lo_h_ zsR@&D=_1MQ)}#J16oVPdDNHeO+<4oIazyI09Z-ZNt*DXVUl$%p!jgu3B{h?wN)F=* zMJhzBFa@~JC%?D)t7`pAmjnG+xKepjCS`q3iKrgqV$Q9c6)-?O{|Ez?7@8k)H;1&{#$%n`K+@`VVI!u!cgD z7pz#PZ-wEHDAx|)moa~Uk%$yYK$#nRE_p0vyu7BAzOM#9TDZ^n;!{25ifcU`2!&M8 z{VKG!349u=oa#0QlMK=Q`4RQExkvi}5MA&W6#f`ok+hO~ZLoL6YL!VL3PjuOk_mZY z^QYE((rD!D5+Qd0kjkgg{Oy$K)V3D+trv#55(fzSwRgLOuJm-~KH{N>qJKA(=X8ar z+RMO}^m_z4u)D2iDgo$SXHo$~G7RMCy0XCfk zlwK$CM?N4cgqPU&YJ_KREMx5I8AxFK#s{(M=c_evb91xaj-mS3juy~-0_jH>$xyaN zDOwOE{}v?jk4p{)5CsY*TUDM-n>Och>owjBPq1llD&`hJj=7Zw6Pew!Zc9Qe72 zfFP3zxD=eI=<22@<^xFk#eU9@OF^`JAVDhzjsqL%?YVuY#Poc_z#(|@=YT1>D4q(g zR0it=&OfYm@_x+#W{4biW@yv*sjh`xfG!TIe+ioZ5J8keI(!}oT&>Y)RFZ3?4|Zjt z+wDTHL?z%bJ<7YmGXcapy-eJpCJb?q`9SmpApWmtF7=CSGgxISMABgx;@};BCVO6o zBbv4MJ&NKHao=0HTYFq@`6}%FwsN=X^OSbC_5%F5W`pN-w^Fyz$_I>y!6PGKWGzpC zVbikTTdBc5=3=AOcvLN76~>M;YTTsF@SX=LF*nXfbm)D`_)+3rKMGk^ zqm@@6dSWz9AR!lT=nb=mu%N_2M+%KQQ4Xs`?|;8nD$Ewr7Hk7~!UjQ?F>l7 zt*(BLbJ`!hDrE?&kJN|nsZuN8R8H<+go&P${ik<9|HJ%Qu3ZsHG*lw(?;ZCmYxty% zuuUX8B{dU;(GWtI(!BAdwOJ@SSMEpQldpOlK-y{KO~&y;W8e7xPcj;im?_drzwsg~ zME7|hc{@NwBIFl99lJ{7cMZ(Qln+nUPDOrwu)Cla(buU+pKck;NMat^qgf%JZ5Xo#E#$ z2-7+scts;?h+7G1!}3;T{CkwdfH%HVf0p{Y4P1{VNPaT0{Bbn;_RX>(d{p|ojU=9U zLN38`14^>JY^-HH?4tz`D<=S?vjC?^Va6&{cu12We=-g*51A(;uj{}(ph)SHdPYGZzi&~|;r&3-;jcbL^$gwx z7Prm$fnVw&O&KLcAybQozh?SSfwvz`EKE0a2^BY9&Q60&K|s>x9@ zw8vWV_O6KjZx2>+8CJHe&F7tfi9Q1AwIFllHj5!-eN3TX&u(@1W-MQBTF}2IgWjhy z@@^-RAqXm$8@yn%qB1j%B!iVktiDkiN`?ewXBFlQC6?`4tnmlhmi$dRKeidB+)%_lkSSy*40pntI(n{ET!pF9T_Z;iV{7joBUG8|E~8|U4GTbkv2fCsWfE_o%~ z4xP6YFH4f3fcMCsM1`K+h^kM=b9(9pwp*<-SyEIB%h<<82sioGIF^89O>qgBg~!Qi z$JZKlg~E}#gD^Yrq%g!C;YYQ-NAEFr40S4mgN2QLKaxOeNPyA>1VZ4T(d6b_-J?aB zaDTm{&lp3)XIqaJIQ`7GAQAjrlGMWvg`Hu4F~<#c2rI1-0LJ)VhaPhq2o%}ae88H< z6|hal?X^AYCElZzc}f_WTM=7)TdhlQ{+zZ$@TS2}+ZO>)%1G>Ze6lF#p;KLB&vd;k{)FfQyrYzKXNS60ce z2Vl5AUU>Ro_cX`Q%`R1bYgJxG=O5*lHl;eMn6%^7)kCBQG|mG6cavnV(+0RQ z<5CmmKq_ndgL{Zb#gvZI@c&_A{c{0~%}H>pb0y(-J(TuWI`>9EaLQkUD?ku>MkHdi zymGhfHCEqrN(dD6uYaYZhKx;DrpQVdt8(y*;66h#LARhSR?r)m;9LUWn_Y^E(@O9f_1cP!gPS z2^_tDal)^w9&p~#TkG&$p^>`j_@DI1d!u9E`?!Nvu>W&0EQivpFFWCdiGUE-<`pMC zg2|r#qBJv|DmXJeKCi5~rnNS%Y`CQCcW7-E6?G%^#o8LM;%GA;A;f(ragEv`7(9sl z(Wq|>Z98lgmNriIZ^AEe9%@Af{xB;kt5@>}yv%%(F#vvE3dvV@XH(u#g@N;?Sq1z8 z6;gRGW;6|93{|jYey(R#3#FTC?|QUe7lDIs*#1KrGK4F$#5rKUfy2D=9({kKmS3a< z#5f&yUtrCsr<0UFE8|eFFH#QQc?(XL8`j0uuhyS8*GRF{h!8`ik_MBQ#{9ZvW ze+m%4n~*_5t<$?0<-rt;v6AJ-}-}qlounkZwK`2K(JAe z4Jo>yF+Lw)U|S`|zx3Rc*v4!W_a7P4_E9^|F&Vt@w+mX`#?D=?_SjM+sOkgFPDCuS z)(^9o8+boJ*Kv7PO#T)JJsHAiOd?Q<39N>HB|AaC8PKU@nh*a zno0`n8cT=$daxBx%DcHqmbh8voQaFo5jnrrH(H5~DL3?0W%0?6`hFpz7 zn6g0j0QbAwtT0l@wJJ;duGp7p2G0B3N61MGI^kr>(sXR+&oTW~!{FIX-c!I42pFFq z)28ojVD5T_q`|7Pa38b$pXcF~06~)DhWMk4YdF}Tu@Xw@KV=4z!b zw@lR$XOHFku90tO(X7a?Kyjld)TMw;wWMu_!1wpZ3em(YPK4ga%p zL9<>U3cO(Bb3MXF2|=_9ct1cosU-aH&eTNrtpXW%kOb`U`Rp_(CH4Ax?9*v+idy|z zG|C#u#r)+q!o2M=^LTbu2J7DR%TOC8QYx2uRFHQ}m1_5R~ue^-A9D8QAK ze1#49BSikk;tRDu?=ha8cllC`xp!0T5wzHs@Iv&5=-&PB%5$P{L?9Yck3v;9mOZYp zcTix=AAIT%>`z8KR0Z&Swx$c!cmO={0NtkgeCZbKlZB`EMOiy6(IO?kA(i5VEy&Ii zuX@FKDPOP~NvhJD1_~lK5EU78yTL4_Vh32FX)*PBkL1u{q*wp!k#uN9?~!XaqB`%v zGc<)n{{%a!jnamgOD$UYZyWyc>2g7?U3QD-`;M@P!M0r2XZVV)jS8kArpeVBE3EE^ z_3kalp{m5wsUj2IG&h?P=S2d~acvIer4)%#+f}t}Y#nC|x_HB8xr4$JpC__VxUs9B z?@$Qe?*k{Yzsot$A+rA(+KEikg@{1Lmjaq0F1;Zd;y6UnW7_z=;w|-a;yCGldy2)M zLq=%ixCXde@@}TsFV%jH^A%oVpt0F|Q8u)`PYkYr*Qa<5T0Xj!GqL6{GF#h6_MPJl zet2c}@beBMM_((%D{W&(ZggH-E0*bR^`F5=!j;5C(NbZ#)yM|aN_Wnc2zWn?+ZMnh zun|Q?$`D;_8s!E+VDQ(j@)$~Z$SD*G+9v4FH^FL)ZF?c~hNVW->=_86@lyF>EkI7a zlHdh2_K@O2OLMA&fO*CTE3Y-^`chRSZF zRgr&}I-wclGD=?bepk@q+2g`rS9BZ+^_zGAu(6LkX?{Vp0p4nV?=glH;4tK4uX8Ca z-`xuA55-f_5|!YRUkjD{HEOjRv%_@CQ0CD~o1!hU-Q=E&s$<+`4p#Xc@BtJZSIuVH zY`$Q?Yuf$-G5BYj-o|l}MnW|a zyB5D3{fM*J=bp}3K8t+pv}ma5uu>H0{2FIjl7H)7`KTE+C}6A`5BBOCJrgZ4D+5z- zPztWnH-QElz~l-jN5H6wGWZd|!K;8yc9YrxdYxexG;Be@PhJ9a8_CG(W_^?nI`a00 znW3QR?&2o`s(@S&&{SSYFM`~tz=Gp!$PS;~+hhE@fhjZ)Jkj<7+@jkp2o;KTDnl!- zjcW@CdHUm}Bh$ zwU9h5rectDWW@CB8 z2%lf2a^0mfwNTF60B<19eBOaTA52m@4P@d1k#ERJP!YK>ZgiPokTojkCYZlk686t; z5JCIse9A*$inV}C{sAuH&ABIzqBb>-lnM|p5^5w$Tjv8O5vS$T9)N1f>6u9*I3|He zgugBiM6vB880O)*>qd0N$}l)E*M#SG(dWz@B(*zdX(Wg7*&GlohcH6~p$0@-4Ipzd z)lO9meH-6w#$Mv?n6F*0Kx+Bj`E7s$?*|^bPEb5Y7A-`4=VL<`B$sK>FZH+EzPi5*`#%VZ3 z?QJyaUD328?hXbt>3#%JS-4jGlLsOHu<}g~DQn}YG!xj>O^c*%8mJaDaYGKCfb-s-b`jLUBn4I~C zV#I`0)tB6!{oUo^FtNd5Mpd^<=aM999x`Vp;f=lanNa=u2?$ zn*IK%I1l{J+6baddD*f-v@&e$Fq7JJGu-W^8}K&)DV-Rb{Jbmmj$7*;K^wI22ipAY zPF2IAg-{CqNH|hPkQxW&B!~rZwkfD-+otXGH3f;I#2>7t5~o^bdLQwk=9 zT}^~~88p1dMS%dYiJ?ynAA@ynfk4o#EC_GzB5E09!oCNUQKQCgVtvhTd32b99RRmO z2oRwt!zq!dt?#bB$gzsR?Ug_wJMmUMN~i~Xd))OZQ^th$TcZfW<$ZH> zomA}JgEvdULep2CzY+8TW2OmVdv|*m*b3bJh_y5Gu)7u-4K?N|wHH(lcid)GY5UM+O z!cC#@mq_|t5eQo>X=%`?4XEEk&^Z6VB@NttC&-vS9`J16|hAT-L`q0fS3Ilf} zpY4ZY-7fm2Q`&@~j;S#Ac5VzfAl;qaYVyzDVeAVK17YCM8`XA~>PCYV35&L$?saNZ zcz$+&ao6G48T?cZG_H*0&6lrFXJITRZQ_dl4;E^VYQ;8KH1eP8{0|`<8tvDF^LinB z9(dbfjt`CJI$~S6Y#InsanI&&m2Ri#XT$iUROHd2cd_AM|LK zAAoh?4Cf9G6@@&3Ocz%(vlZZK`)RdSH=k&*?8MVHp=>8jz_lq$O*?;dXKro$A#`-^ z>&aq+^_(L;gI+x)_E^%I^fgzv9*-Jx~3h)H;J+a|*l?2&RslTh`QpxY+ird;kZe6t!_ny+yUVwo$sQtM%-p%ufTN%haH zC%Gc@)#>_zc5Lx7Z?ow-b2PDG6pirbu{C*6HpSui+g?x3gdg2cz>z4rJhsdAR-C*k zJS8i7m83R9y{k<384>OzI%%aShl*?j#)R}?^!U28TQ3RRl5U2QfQ z+EpL|mn_|{f=P5K#&&NqMEShx&IFYk;0Bi)ZC~OeOLb?<*BX4rz)?85U^~ORjYWN3 zMs7R3HY;bTo<}9Ht&U-(#|w}e3`iy2sisYbZX+Y3mKeA-OO~Kv?91l0Dm_-r>z#Y@ zP>w_bInv!X3jtxQxNc%*p;hw1iK8>bQxdLJf;^7!iT$Dm2SM2N-Rjf&*Es_uR>EeV zb0__|+AG(z!67?;%~Ismv9fd2&TqQ2C%kFW-Fxg{lXdzYlZUI~$V zgSYpjy;pOP*ekn!Ihibc4joM^J6TRM--q@ySZ`_FNUloX(f86@J4ilNHr``7@9Xdy zDL+^~2|X%5Lb*Gf2I|e_sxrzMSU(e>o`jr_{kFb!+19-Tt@++zS{Q~edf8zNIVn75^6Jik1L?zkRw=(wSM@T@K_XYMFCYUzD zF{d464FvatbHzR9OtZ0ml}!f&7!FHXvCDDg_)5}uwUy?OS7G2ss!Q495$;#?E)KF zh{!kflPT`bNe2#%v>AXkDKbudJ{RMkxz@C!9YP5KL33F;R3DImqMOq#)gaGyTrSgd z6zt7JSokwoi^g?Om8y2;X-dXW7%;$#^pT_+YFJW!GBhG!|XA<|#2}Bsfm3 z8KQxcuH4k|MfGvKBLpjG_Sg0P!jiyT1kzTYiynR$?)Ud&^5?Ely;%~(NxVf;>&(iw zKdBM1Gj;6xL$LeJ<+u?h#T8blt@;L+x+{rM4R8}o+v^bS9R%=C9C8Giqxw;KUAiyJ zMsy{w6*pS^v#EyyHRZpCe{^nybj-9ncZ!H-2B6(mfQibbfD|Gg3ReT^SzpuDMp+Xk zI5XxLVJ9IX`k=u}d^(vs9MF5H%(zdmV%C|R!yCQr8n{6iS(KNhlE;J}6S9+-;H}uCCCFx$q!G-I0I7#J5BEvmr zWtTA+*NNe$2w@pl&G&W~Dd8g71)Cz_1U~8=@lfKL?wH(A>MWIw!}o4BoF8DH%?WTJin7joO5cWUx#t?E8_Uf^8?-5~B3?NZRAz3@o z5iHs7PkcJ(Wmgri>X_a<=pXph7+Y_=)1hzD`&}KIIbmpGeRc~Hb{G$_+~8fpUe=ZL zcYL?)bvrr56eo@hOYUHAO09D1@mnq+Ye99OeI!wq6bkbiF*j@wM*L8ra0E z2f)NWZh4^1QqI;ygNiYUZI^2GANiPhPo^7+-|c4oa;13ZBt~_{+2h~KbL3s!`ygLN zpgIj`^%{PDVKXyqZRqu%J(Q&Sz~ZCTpX(g!!0-7kqi|T#M~@Qs_H5(INJu5eAy&SV zRfd3i=FeVNbZm7*QnWg~xFCjb9^;1TA}x|H`%0?pT-Ps-jgFq9Ynzx=9wIglu@tmJ zRdfS_G$aON$x$vW%Cq?@4}g{*7Z(Q@QH4Ant@1QziTK5>=1G+2qAyCVa&J4JvPM`< zarqQ}I?PM43*h7;s)>Vd4oy~uV=ZKaS*Jz`QQGFH)oaxFMOB499ZaKdGu*9|_~Fwa z&ko0oICvG^yP?}rcm*CMHn^Z9M2003zE-@IK3&i#KN))!uDp!^Ek;>i2j0>%dAsWa zWj61?$HobHZhmb(b;AK#>-UUbc1$ocHmk+r&{9-`_hsm}czW=!Z(o`Y^J%OqH#vW z<}>bge&!>y7dm8_Ho}VQ85c)<%wf)FqA)+F=oc+PmMo$qt3@FQe6Jo!lY`QY9U>n^ zwP&n=V$5Q6)Zp@`V`j>m@KVl&A(+{DfGt6Bn^T2O4Ci^_laHW#B)W_ux2!P>F52Y) z&8BQ&zYM%2g~q4c&-^5c`Pr&0nU@pF=M||aTH=9nTozq7@v$LKjgkUR?r*kGuo@)g zuqG;YeZf7e$##o>C$(BPm0jKJ%<3FwaayR3%=T4rg|b%RL$>;{&;-7r(l7_B!}KY@ zhmaZI;)X*EL-AqHQWV~P>71O2IVFkXVLk-KkKIcc`VdE~4(2;BbiSV5WgHIx$VeL_I8_9UsXrs?GqVXnj)# zP4Iag>JD&&v>r!uX(fWgll>yREelymjX%8L9lZU8SeorXjY@oQOr69Ak()`YHME{` zLXB-vjX)&BZhGgt8AC(8(x}vTJ^um%Ml#M2^4LulGUwnJ`Vi#8fz6kAV(#@3%E{Xi z9H7h86eJaKLr(8GaPyI(=sL=Wtf9mI1TLK*@MA;HG=)2zZ&EUxo0zFt!}%#08PMP~ zi4i1t{T+sCGGyb>lAeAJFrdIvh_81SCYlcZ2&8+WXR8)z2b|`r!Hj6B<&;HulZGZ8 zCKHn=#v4s8gkfgDV=j#5qVdceqf4=q4F9>3HY>(3_=5QrV8GQz@o;G=ipNkT$&;tp zDfWJGrMCMrpqJe-gBeZyYZB!=l~yV3a`T(Duuh$De73GwHGycJ_B}Q^)S~YBKetS= zR``T2Otz)kd1UgbY%-s-nVheb7;N>t6a?`16oF0=G#B*7gXk`pa|=KucF&_L`V&GS zZvrx=uDkC{=fDpk@8|QZhb!}mK;oYYlJDuouC}wUi7DG1g0f6GD`6>is<{qE1$yn= z-=O%{K)xzS;VC-u({Qez}W!+9~(tf4GoAx0;(f<_W1#xHf`nbURqwN@#n? z*otd{LncBVGZQ@%R*!N;+7lFVx)1m2`yQN5DgXo=bkqs4?_5y9-MyYMmL>8Z#5Opa zA1}1DSd?EH8s1!;EHM~7ObzkeHC)`iT@5Xa$ydwY$~l>hd9idVx<}@mP77 zRe+lnOayl2b_FwZIOFkrtLuF~1~AhKLnR5^#SiVc1u=k6<)9Kc_o=SB#VpG=#(PrF z@v$N#&xCz8BEB>fP=8=r-3u4fjUdnq=Doc%Q|fF#a_S;GKvJBWnkdcbiZwq=3&}=E zK*stuN|(xgQ#inBF@^J|uq10&(PnM6_=?Q$@+!miY{5j-BV%Apo&wyn_o=VQs5a1A z|0j(bV(4#$)D@hRGXkOMbJJ1Dk`8OO<#yR_i7LstU!74l^o-O6pzHnaJ|L)PBuM%P zHHS9M3owjQyyyw*<$(K#WV`*%DTxUph)U82cSR{|6oh$+pf*ICgYrWm_3}Ii?PRqT z*SJiZb8jR`2y3&xxcqe|PUIzv&LKH{9Z=ph&5-pWN_4ES&-|FCyJj&6^NWB=I;^il+uM!E@^YJ;K;lk zmV*VdILKP>_}>ZhnUE%gzrGm0_@S+G-2$W3OD$)h}{dwqdQoOvdTbeoRZ| zcieixnc;c7SX}OT9V^rJGIExu;Cr7$pj4{ef=OD)^sYg#pHidi!sqSztDh0()*zX$ z-`?)Xif4kDw;y1JJM7`Q^Y)t3rd8>Fn(@~2ws5{G@bxRHb(7n{Hveq+ zhD6(w;Tj*{8Lyx)^0nx5$PNSr$P>E0Q8Uo60spYM`H*LwLpk<2#qTfyE zCcnk2@xD8WP`wel#9A9oYCR&E%4r#M{|}LLw3cyye5Yij|AM!&>@?q(cwQ@d)u)WL zSJ*WCEkfxZ#D{JQV}Mb1rOnJSFltZGU$Z*#D%XGOTT8gvZ*dw)zA0yZx&8Wj)PZoP z&PDyf^+h6obdJgWFapk;*}kNx`Lb#4Xt{Pu;)me<=9L`(%6T8DklpFx!I#a&_{1q^ zdY0AWFYMCDnACaSgw?)lJI&+dFpy1Ox**KYGUMd*ez&u+G3E3J{Gf0lYSIi*G~kJ| z5^U@*F|qvTCBF9<``9%PP$d5@cCLw`o~TU?CB~d|4K7%F8+$7dtnix3`w`e$6uOwB zBrJ3i$Fmx|8PB52>#-^EL(x0D{xIuh>D24~)&6yhN2B#Be&?q*!yJ=&IDJ#qWp&0| zKlyghoWa|upWoibfKrB_cFwk&y!U-TetPmJk3Tdg#mN`*K3J!%<&U3cR(-6ckx$0< z8VHm`WNh{TdRD5S)#f7FS`Y?4>iflH@^=!F1C|Zw-#cY~<+oPsKAbe&{c+O1|79#u z%(P!N#kiY@*{%3*Yt@RLFoR4UW#=b1*mT>!_mjKw@%Db}`atarxF8K|^?fJtw0`S& zdAsiuS$6FU_i)}_^L;u&*S_!w=$!+-k4rKlYwTtT5P_oZ_2 zEcLeQ<7jIh%z)u|((|^+%a90}u>1Dxst>pKb4NP_59xf%OEiW=20)Q$<+;(}eT#_g zvh#XvP}k=-e7a<0738wLbe(%MI^~?=N;`#Wv41Pef(qlAeLy4S6{lsJ;3H3$pSUmw zb*QKpo^pS=uAq{>eO+pMS)ux|`UZT6d+X^@JU7P#NIWZEd-Y+bZ2}3=Bk6ocZ!c?Y z`d}NGo!2hF6|F2$z@+y$KmGBjt(g)-E_?@}gV8$7?iQ0EeG~&|bNw zgz_EV+Jybv)>_)--0SI_UpV&+a3Y=Yq!v6T*uOj-32=Mg<*)gjPe`ZxxS0tLk??+3lhl($UeIg|yj@Pq9UN6^AYd#(~hauG#332)Y*0Y>`k5k`>cuHRee)xfK zPFL#QLQe!l7WIsHS3fZ+Szc+NskBjnZj6M6%>h;_B2=dCT{>ij>+c%I4+`wN{TR9t zT3@<xLqbYJfP6hS&-P3Ky+3U{69(j6=XSYmu z&`%)K@GSz1>}0uA6Kvk~U7BWDVB@q!;MT^^W1<&r?zmLT@9cZe6j{CX((FjITxELub%OCrjsm%{UF}B zdH_t5k!_8p7POjLDNE3K#p^&0ozUb+q;wP)?#*LSJOMeo&tOb*NWgB>mi{ogchvUi z^-5svOk-7U7fbW?TvthmYm4>m(SFtAEEgD4x82=l_`VJ?el0azyEzo`KA&i6@p93> zJUmj)j>mhqgx(2}ZQ0g(XwhA1Xb3KmYdck9^~eU0$Bk7vJl3Pt>ZRbP{Ilgt@r9W- zkB6w9IsKJ#@ZovE5?lu(nPTv)&-LzKXYh<9_7>x=zU1KuJ=OjY5^Ae+2=;@Z2_CD# zZFR;3GJYrCWO*?(qEMqHnzXL@$;oUe>3;`otlc|oQ2kELN)Zyl$F`6w)m|B&I3V{# z9)9@QeL=Qasy>Pp2#X?OFOkk5+7l7l%Hdf{=02!2lZzK0H8<4f#-S+_zSw9o)M{|w zqCFF0dnKhi^ICfwjiHb@Z3!EldI)QKtd{weCwS3aL!vuJgSFva&w|4bGk4q6@b(&& zQr>#Gx8C+TO4Q=@^~7mqW}VigLtSnUt+n*7Q_9)*`WIPu7s}oDR-p`d!P|8oFYu@5 z=e{=-k5$j3D8rX$xF14-!oQ-Kb>F|0spLo+Xx$GV7r&i1nIYkdhWdEjqP6+B)bn|M zUZW_0N=coBFv2LLQ(dtgE|`G3J@&TinH>0`s)>P{c%{x__Saauvw+imyU7nFV!-G#xVyZi2DWkWsoeLG9RlzermxM~<;~CHl%xP(%Im`q(lmL=+0wWDPa_wxAL3u1ew@C2 zzM>hSFBvkSgcQnp1EZB1dlg_3ACQV(tKHJPbD=*fVrb5Jrx3tmnLYRozhb(gaQx+~ zWi~c0CrUiwg|L`eS<=8>(Srb21oa|*n|wfhh#;~uP4j4gD<~UeXIt`eV@B7 z(3Uek<~e(;b^Q>0?J|5b9sHxMyXtkixAwLgT$v-iEReg~e!KR1Iaha0#Ab7U;%u&v z&TqT@B%0<0Sm^=Hy>d`Gc)R% zkSKz3N5cqXK8HVOqq&V}$b?HN$nXaM^SQT`rz>ZgPh>jsBZhSJeqhG_gjRv?E$5Wp z+^NRu!+^})+xpVm`FswV$iwzjADZUvzBVij5b4w+m*Km4nDL7Lp{pC=P9a%{2$M5Q zluf5NUkx|obwbJaBQetItTfGP!Y}8xw;L|9H>-=5pr#eawP2YaLMqjFzY3KUs+qDZ z@{KAp&)VAVO3z;I+U!>wF^Zmgb?sMO=k(_c{aWT=iC$HczVxDZ$*!KEJ0D(KF($Aq zHhs0{m-y-z=GGo?@*-KC^Y{+~vtM)F3EiLEIE*UMX#DNdYL0ZBU;<5aGr%;j$O-~> z?i#Dk_4Vk90RP&!*>pwLfaLLv=Bo?Pc$#PA+*0}-?Qj8NY>u;>J43JQEU&EQouT;W zleOoavJ)aJI`^}BfCTh|n0>fOSB1bK6!qB9^?JOI%*O$U`*{P-oNq;gv`Qpr%LmDR z|BtJ;49g?fwsk+;-Q5BNcXxMp4eqYNT@o}9+}+(F1W0g#yF;)KC%AJv>+G|#@A=0M z;OVZeuAWtMjCVXg^(?p5nO#g7NGE*X@$Py0*>&D?xtpA@<}yy^bhK^`cmV*h{^ir= z%VZl+k7fnLB?x&0X{=WqYLNQev~c-(pW$=+?`pkWWdW{lVl(2EuArq;38=VNi4>2S z{zD&;h%RqzUB=4yn37vm(y{!$C;1X0h)&`xa@lusev}#9FQUAT;fu%yIu;ZPhG9UM z$3?$dg#Qn2uuL67gXBjS4+$IlzEB^dPA_0FJXpDGq$LeN#?&$cOkGboiyMXFC~Sfa zlkZxsux3LOKVTaUxg2s&ruJI*c{})Ou-?)IXbp=7vXN0lf~-}~vmWmNjq?!DuqY56 z?yVfUzsB$4q`lI6o$-*iUh^G1eC{Xjq^aSV&l6{QNML`m6pT(MRJ0O zV*iX`<37+)TW$5Lspb&wG8};Sb$$f z=?36GBHYVhioOH^706&=hyff*_hD*QnR14#q0y)`6C2FU^nLzU$8am<4E}(DOQ|P@ zyMbB0IwM$|pl%WVW>2Qm|C)dvFbzq86o=B{28;@1?eI*oqMO7S)+F0=7b)NKMv6ASeyQEru4zg))%&QE~9zF`wDc{RUHfU~0Gq zHuD?an*ms~!jFI-O!)QQJTi$o-fC9Ln#PslXbw4rN-_emkGj9bAvyXRBx-gie~P$a z7LU@GecDXZT?Y;-#PMs)!uS3wQ?_b}LtbBt;25G%2-ntj?U_KHth%c7V^i!eLk$v>*Yi=4`_*ALn&pbsP{L~P zJOE8D6L0e^Z=!#M2ZN!q|H%E2MjV=^qWlNl>Wm?hbmmu;r%zNr@gs`>R?F9#149`e z54lm8=r3WWww>{b2%=UW1=ur0Y4)ga4~}fb+jYxfb7?Ss$M;$-KqCu__SaKL8TOsa zQ_z5>2}A{2fzRrZKO{PFK^@}y0CMM;8;SFZ!pI!YN?hsQhy-JT4yuQrsZxegQ0Qj< zL=7}K)2xMp8kkrI@uR$|B+kxtx4Y?@eDi5-VKkl67#GiCFA|c(z_iqF{QlpLVNsc! z+;g#HB2K@Hhsb=u+hwIn+F!9L3Yp*K(~{^=cPfCp(7KD4E||fod!@u_QAyF27)&bJ z*~LiQno2uv%N8^wMVQ5@$uksTF&TpEMf$%uMTuxtG9v$}sPR527z6TA=1_c6aZ4JI z=dpIGvRHIVRaJ%mdG}(grh}IeU)pE)WnJd^li>`>dg0`Yp!>d6vViNpPR~35J9#{< ztD1y)=%Jf>DK4Bk1`V9lex4H}M^R(Ob6#k7q2GPG=mx6jt?oOKkM=K-30DBv3Y-KRxizdMu6EP+`H5|6o~FNOX_-uSd5-eu~ZPN!Xn)(sI$RK6#W6 zjI(L+JVRJ0hc?~6hOp@}RTd%k>P z3AHoRN7Sp3-?mk5TiEYkM@io+K!0OAD18s&a}op5pD)B9h}Pn_rKHorg3q|!@Bx&* zgM>|rJvVe-iZhLR|5u{fkxJcnwU>fvt7K@47&*meGRQX;Kgi@GGw;_hhJH}(m&E6zczfY^-+DJlY_MlFp&3? z^GertutM2f*Iyxsq8c5`aG~1J8trSRqeGuYMnUQs(FFSNA?55_nxGa>G1v~}%77qc zSTKUijcK5@gpnmE)2i9}^RrRJ6t=se!n{?n#931@3+S0wt4Alpyiq2UPe*!g1|otzJHT%aa{f7N$sVfaa|zCWxFS(zuaj z*H;nAw6a8DtGHCdPfbOqa#J6Jjx`*{Df%aic5CPmgeE8C6R$2O;$%EuE;0~K!z6!f zaj(sYlAYn6@!(I$)M`&mMa4*tDof88mcRxFRi zr-K)8q%K0DezL!8sbfuRsMqR^aaQr=mCz#76Fz^{fTYRj86Mu%xYc^n*c#-_-Yc=$cSb9eu$OP83JF8(}reudYg8hQ?af75@`y% z6CSlQ?S=?h*eD7c95{;=`pwvlq?9rTH+W_|7la-1xep@~!MTv3jD!zXTBLk($cPUC z3e#AZP6H|Ivo2ePJ-T~bD{98^Cm3g;ImB1P_u;aQuoCZ!?%xZKHVNo8ci2Q!|vxgK2c3JZjWlJY!a-eTt=7)<6@02X)qpNF5B8xYAt z_mREG$K$;_^gtt@Df08-PA2CsL!wYXt?DQreQ@A|G0mkaiO!z8GA%4Ff7d59@86G3 zE)gt?fQwE@%oUB_M@(mmz-FMxZ-!1O&96pH7GbuHDa-TlKHOb${D(>_e%W{BvBgV> zXj&3FM;FfeVzJWfaQ5vpUtz?y$mi6=;78FB`n2FF{ew`+I6nEM6!CZ};z~hYiwnoJ zaFC=SKNLKF5O|nP6GT~9F(rpvn@Oop$p0CU4e}JlhTVfbrmcXdH-ze)xC(pN_t2r@ zOEoNhCAlAT;m2E8(eowYkPzRJsLkZJ!6yiWy%M$;E5G^hF|L4iGQY} zA+L`Tlq{$!iNs7BP=wrz^SB$-RUsW+O%B#{&QuN zDSoOYAd_sg&2-6BE~9`^*5e;T7V!yN2$6Kzg12n!$|KoMLljL_5`o&ZLrABREHB@_ zfjX6D7N?eFHk%!{#MH)>Hp;ve72dbw8{Uf6i*drij(9CdK;YMt^~J7K`}aP!Vqjm8{q^IoV411k-V- z^%PL_3ip6^d0U;p^d2xnF??ck$oR^oGzXW4`b~<;YM2?-PJ~m=os~GPXP3gj9vgz6 zD9NZ33jG>`37{iwkjO*l2lH^7vFdLLmEhby%1_Qt6a-NA*@U>lOS~7e0O!GbkYCr= zNFobw%kxn!R_|@qBvhFhZkL}G#Q<2ncK;vSKWg!T2r`e5%E2vkYUy|2JkA$DK|WUY z$t?2~@#PT3K__HUWWpCS7yh3o$k2h=2O^sJK0i;cm4cgHkzXfYrtNImBKAEbbTi=r zmHc2T6uNzf_{^xrc*JKefA~Un7jpFo^vq$kX#G!;653g4SX>1M6xxbD+j^0Ko0(Dg z38aUZL=;w6V!Y~o;)>!Eg`Z6|*mL=iNmhVuV7p3WA=%4USbz zJnAunq*uAYD{uPBDJX}hT(LhE)%$1TPxSb-2u8Dr)VUI#+Z64^UC3t{dBv+;Qu+ot zgcSu%@~{aD7C++GIbHt+9Q6y@fxT|0@UktXAS&@}7X+nvUUkCU7}Ox^Z_~;#n1|5J z|6mv$ONJ|u=t$%lAWcKag(8DHLwY%j4eTG0TTvIkwxH0v#QR|i(rAZN$qEGsFMVd! zV*v^`H~fJiKM^G&5WRK~;pK`#X(v?YF4jKvlyK`oAA%=0V}glEVG=2esW=uv9$xz9 zm^X1((DaTR?H>IECseMzqD?l6#MV&BUYutrgg0?;39x5dix-E%(ZivAU&jjOe~_4R_ZcWGxwGc+i6!^({*^rmJ%;Sg99((!{G@%9e_`O%=qcFt+WhZ zHFY2_JN^)%$T7Eu6@2SZy`EJc|5XUmT;hMcM$SJW0E{`9{`fc-Gi(&F z#InUGcvO-lqj=9SE;#&hh#U6!gV#bg8`_!4-&Z_NLez3};%95~s|3thkHBQfj4$Mb z+exXI8V{@(SkN1;!l;vw{^g)G?6bX>PZzJ0(;~bPb-~-DqW@R+Y$yn%doF;5It(i^EhrdQ-dzRe zgu72Ul4PRbL4mX-*NKkmE07P*j38q386J}e@a5~)iNs}Sv?S+Dsqz2>Khmyj$V z4EO{i&nRk!K-2+2H?4O4s-t*9rdcuAsXy6vu1T2dAWhvP1M^*_ZD&VpD&lAd&Ar?x zbc)B^Ff_ni&;2CIO}$ud^%ICPxf*vHEVK9_-zul@065)8A8q&z(^1PaV5zaDwmFHy zllsac%0gMc?Rg~fFO!dhdaN>UJeo3ZLs+b#d`l73d(|+7XK?{YfuP&m6tH0l{u}J* zFgiCx`Vrg*N9HT}bxUlOrP}yvm?~jBT?oz>Zsqmt=^V%{bQ;g(Qtd5OA|mxWX#kLr z0MJANP!(M83S!h;$n;u_i2(_gsG6`F_8)u7)oC>PV2pXrxr*y*7>c9Qy^yY4!J1rV zQ{HR8ude5DLp6&q*dvo@He#IV5=CIX--QmL(1{lluYZzL)VnZ7f}lL`q^=z!;IB}f z=uIx9meosErYiJ)XBUcS=VG7n>gWnyYXeX||DB``%n#Hi=rT=8{t&GqvOYdOlwvww z=@0ptPR;F!Uy8&SS3T!{!LE$O5_9P1720-0Cm}HyitZeyWr2t49Ui7OfW%>xYc4KU zh-h1AQJEM}fiSv{LL=flA)oIY5YFiP`+k}#3XN3A4ZKtkCM6JfpJ<)pXexsmtVO~H z@AX50&+DVe(zJx1GBMvr@K#8X3_Tjzl0i^qsX|#TptU>ZuC|6mw^wF3$hOHGyb|OE z&QzD0tkJS306Ytj-hhP1Y;iOQNb?&3KX(*a;QhMyCXN)K=yc2#7b{iD@;aKMb1})! zEJaAbnb}U#ZVQ`*-15^#4`zgV?2<_^-cVuPKP-R0Zwo=+Tb*;Uzl&p7XM}>{NhO2o z?BF06_{>|3o%XFkxPXYeYq$6NXOZVuo|d@rCID2=;XMr`N3++zjUO-6_<g!NE|9@$;${uHdB)uZ zE_6m8$24p8Cb@d^V3;-^*Bv;*dO-Ym&|&=Ld?NT+lxfOyKR9v#PR3=)*ew}zR343@#gD32PDA44~+3&#^S~HPdGF(2yPqQzCRB5 zOH(lpE;0Z{NhTD9a4@zMnT*ZuBJm5A%sQXV@D_zcWGo@q{$|h3=}QV6x3)teT4=51 z)3?G;wwalwebhTc3Fk8<3cxFWFrMWN(074&j}*)3p`D$21AprP3hA(^RdY@JLLLkHpXA3>etMY0ec2LlS?=N;Ml1Y6|wufkxGjvVVV_FCX z1aeK$L>`CxguAHFIOa2C$h!!y1eo`jymn%>dp3= zyyLs9M`luhl-rbh09MVvTQHPczSQczf;D?Ek;S)jc(`Fbm+b(k%y(sn$>lz`-6Wyp zNLoW;7G_4GNMZiIdjc5G4EL=im~G^#K0@+Zv=^%bHe3_F*Bf+3n>+vk);iDNyU$ar zLws1}OhD;Bfaory>%%WlC^&i?6#DTglE?|X(5<59GwHPMm*#n@X1ss|X!Osv02-Y$ z9uSOpnysy^z0guEQ&t<$>DfgAbOs}xj-WucFDsE5kKa1vN1-fYBi)qv;^B%{tiDfNII6-sKIhJQi%wqiZv|O z^Sq2ftThU7rq=*IqCdQx8IX&{p7Vf3s;084s_P)vuhnt&7FgX*Hhaj-?g6KF5Ac){ z4*>W0>-|nbpDEN0M+mzf5_pDYv6T_Z$@c}t=tIy>jrV5I!Fb!|$Gh%?4WY2RO}~Tn z&=#!o9<=$7Pk_$n@gnSQ!)NCPC9gV1*k|;<>-^8tOB5Pe^dW#l`nBzItHo7X zs9plitfs_}Jf@#|a~JgFZPa;lqPEast@9DVnV#i%w#L5B$!MEJqE5TcI!n(-PsMJl zmoDH1X`yBeW!>-kVV|d`!|^GS#IJ2AnT=o7@#D$=dh1NvcVWoklgU|2-*{1Ds{XpO zU9$^6@d=k43Z_?HbkY!Z7TabQM4y_K(zmc16<|6`@D}(?dV>v|IxOcawKhDVXMoC6eN7^>FOa@Oi~pm8Qi|!c zW0K$9_h|zuOU%OA2eW>c=b%06)z6?nos@k@Uj+Jc)o8 zvc4L2ur5(|QIp@$zE~Stum-(1uf=g{T+s8+`D7ZUj*7QnoJpacp37dAz&}lvW2u0F zYf*uv8+sYwPylg%h^QU{0Uj%OIcN9KJ<5qsxV4yUlvmf@+`ogI-wVPeL z%SE+$bmPFGfg`3dP&kdZsmW+CX8l|BKrWf4#@Fj`LC8QOgH!OM(GbDE?;R_16t#&1 zJ;c^HO#RdKA;4=mJn*~RfJ?e(Rl~e~66hd*5%1UvSSmk_OJFV9e8BuERkS)TFK6&+ z*)94#g$6vP&U%ymUAx~|rC|LzutH8T!$0d13Ft$^-i6c-IlA` z$wzt<(?wAqfY@{3QH#O-*b61s)YcY9xEPgalc4qCC@xQgGqTXN;?Jt6s{_oJBCgTt z*FR%aMB-_F+T(|&VGJG+oLd=W-feTUmP4geg)=gn>}GZt!rKW4JYEB_^uDvVr_00# zE2#5L|G)Ke1WELy=b;H4Ep@(UG$t9+^c6er*4BSYn=+NSgltJ2Y~95436c5K@|sH0 z4e>~uQ%A|?@Oo8LGIWbilJOJ(^Flpt&ZvGc(9)7h0ja#0{&9>7_3AVqP6E9G0>ZoV z)je<_1+^kY^(@*u>T(tuWZ^#HGgRRh;?)684cS(93B@oy^(l-nR5Eh;_i0X{XD6%Y zsg)~Oao=e=859-Vb#=mi0tuy-^#-L4!4d*}W0U@=f#2P9AInsI5iG3AE`nOoICalA zc<9Y0rqyA|Tpr-H&?P5L9GcG8KFvAMtuXITu}sZ&5t->?n1z?GdOMQxd1*4@0-rCD zt_S0y@*fbeZ^3L)AIT!Eoq5Kk4bT>6bb=pTu8HL<5PzxnVM`s(SK(o&$Z>Y)&jsgx zsi0Ie5rn@k@;f!Z*0{c2eVE&Ye#!2!Zg9bAcA6=c12UroUG8-Szl3n7w?_Q0cFkv? zT@#uJ5BU){@wgL}O}*{1)5^xxrPTcTHG2%)K0k13t-9 z*i06Ir1B`hfmB@5j3Ffo75N(wt;}FD(0k6Y7sfqUm2L^92dX#Z)i!V125NYFB?_An z3IkL=dHnb}jIdg4P}5$FeJVDijgSe9YxogHr}zy<3=%q3?q@F152yre&j9NEc{@QT z&6DB$_jHH*!KK^L-w`jhN7|aLDed$$KRj7s@61x*xmf;E(nMtl*%P{1p+UQ!jit>V zW~AW#=?Kx%J8HF0D-Ui_}b zosa#~J41h&_@974wDRg|L9gEzbP1J}JG1~!Qb9SpT-f*e{a0Ag1`yNkt{iX8Qe(&Y zI8PrLJlnb=_Tx;Stjh!b%hxV+d!y4<_QzrU{Z~3_dSVkYh&SUZSw+>JA}HdXXFZLmFR#CP&_8*04RmxYdsx~Mf5EPVeM@6 zda~5Fb|E`CSLffbk`tz3QM{_csvUkyQ+*IUXraa{gI9{Qw%+;j+?qONn?{7(X7?d4 zUC~s!-}0iBJbpxATTA1G`fRr`NhySF$Cy={D|9U$o1!SEz=vROe3fcKSf zo={JsoVb)`FT7S3Jad>-LwEQ>98PpxwmL)u+f!dOuVaSH_!5)(Vt-RxL#6fBF+$yg zuo>h&R}eYWRXMHu%9?GkH#$U+q@W4rAV~|vQgSB>7OC$HG^e79%qmh>BSCyz1hPO} zD8%QC1loX5rC-3Y1sp2z$EQOxW9T3O967YmE5H`^Z0U)=Co1r%P7*WC?w zaRJfxbUt}hj|CGR?D^Qud%+dl**9jbUvfyZbNgH(MC2p!w=^z(3nbGj8Gd)z-Uxe2 zvh8f(XZ~-H)-I&m;Up*&{!$gNA%3ad+s|?=h8O=M4OrtOIXy}$|w)rT~(UXqo z4HAip0~jg=E>iSReEw6^>Xw#!^& z+oIE%GfO+9-u}!Va#>K`aEP=Une6!UNyWVVd+o$?P5%)#QtK~lNwj9Ws8oqKF8(~= zsH4H4gjJ+~FCv6(+Phe7bL@EQgLC84_pw=#(qIEi6bHe5!&1q3$FuH%C1?m_xI?Mm z4ML^V7XO~B_hRMen{e0lW`n~laiG!+y8LR(kjk?1p@Sv2bRA{~{U6eb8T*3(L^*zS zYf(e*Ilv{3Ou(NPgU%1dvC^&Q)0UG!CNx`83$4Z1x}qq59)EFK{_Z%oUt_?i;v8a< zCC^e~FwzX?Vy}(jg@_t!r^JCghAl-J$OM?B0md%K@x%oZ-Gdzis#zXDt)jaEdP?t& zfW=Q(9w>O%O_i0O3-Kchjyp?jU7lw|DXNJ-OOJ6X8t^az#H2}aa&+)&8;eJIwr#iw zBy5%kR1kzkr<_k!W?M#swWh!N7Oxxhzx!ALVF#}almB86D#5t4qy>?zwXIUD0r>*W zRPdr(0Dl%ci-aO6Zb59%`EaxDviCdFYAe67K>9u;1y7ir_$h>D7)b&9mZ%c)PgO1n z4iBxAf)&fXbtwv&=4ze{>J2s+@{2>l-fwy6z`lctN;(cX>gUtld6e|0Lio7L$jLOg zQM!*(6%z%PgfuVJ_`DUGl-P*V)F$EYmrKuGW0IhmuZ19sEWNsoBmeGvet$eND`Wf4SP|J_qrRKLKHdlhfbj zagNMbomYHT_m~+Q`W00)WXf5Q$Mw{PQ=TUT_5d47@Pa#DmEP3SDk)Zc=`aXzrn`W#|bQ z;(685oztB0$I;pJYzy`gD^LX=S;4CLtYZQH59Itp9Am_Pr_Z)nsvtWub1?w`r#wA@ z9Zbw6uR}W`#(?*=38LKp%>eFXqov4f%xaZMQOY#V&U?!hFRxvq{Mx+l?UTLUb(gu( z7{JQPkMBTe{>d|Y$Yi%R`ee#_4v3_7!D_;J2?(N~V>{lJ4dz8W=oPJS)|P&Sda5t7 zdEa@&3v+MUfCyc!E+gcBJm#6fe9)ZmAn;rjK>%ZuzZA49_YUKc&PkZEG^^n9ZwsQ! zdU5cx(zW?230)_1O)S(-X021R{R$9e~4tgngu@MzQGgdg@cP8#U2)GDI0QMZU4o#TL z^@OPFiUlTFZHp)5SUI=?XvXQ>mAoB?ka2dmbHqcT%x3Zgt5EMr6Eq-(EjfbCnuCh- zsfJA6HCK8G5c*mC+uO~+8Um?iJ;2}yl5&A zpP>Rn?u>x{0-f$s3!tX{Gry7=1ZIk8K|b^F9qPjqGP{OYn)mw|8 zksJXNm=^5oWbA}p2*g>hh_=t2)8}8SZlgrvUr+_0qDM13@)fXEK!nS4b1u8^Mv%8S zaPEiZ^p9Jj4cBuNnPpkM0hkRR>%Mzn*-$~<0ByN*eNdzr+%gD07ImTtI)85rHx*tK zh}`(^Dc^fw(!rUBY<58BN1u*^#38_;>214!`KQorgqj+}w$)*PO0uB|P41C`RqV;c zf)$ZnWksA3XnlH214>c%x7hk$xiXiBz?4B9gzOoU_&a5K&x0Y+yGUw&mT*Ir!_Oha z);P>~;;4^yrXcw*7(l`rtrYY?E^tGl&r3~Eg9eHkFd^V0A^u&Qe~lPKIY}%`AmD_f z?pNeHyDWprrwD_6@=r?#^?!#1@LpKpTlC=kC|eTdacYxaAq_P|9$`elg?X1{iDEI? zU%UrR8;4SzmZ)F^Hwx?X{IB`t8}@g8Aw2`YSF;oU|73I)#Bo~Piuc6hzKKctwt{wB zk9B(GJ<%2HDh5laKq=Z`p9o!0e-hEl?Sal2Od9(!m{v`JMlOJHzbj}+qBi+AO+tZj zU><}NQOSZgP=98}2?nEb{aP^w(Wldbrn^35LaJhnl~%CM^_|bO7-G9gj?Dx(y1*uS zL;ow-pdUQ`&!ZuWTdDwkWyA?yvfg5Wf+*K~vHbuMt-Yge3~80w`hL`9;<4kK0a_~@ zIc1gvhWNW&H}p=<(g>%4B*R2mu8(d0UdB@}>mJuxD3fr<3T zrurZ*fCB2@|0nOIW&tfTual*<_g{nBtnWyd71!o5Bk`xgX%rBa5K$T!*=FLaM79=k zr4C0)XjJN#RALXWu}rQ7_Hgln%X4}4ffD943=S4D1c;BB;+_dkb?pm1K#JTU+}Q62 zG?{B{&S+JB|&Q^-oUu;qo@U9a3x+O>6gn?$UgdLGJ}jHE$*q% zTlc}KSvyj4A5}!MGp2-n~7EEP2u4Eut`(D@{euf$2ozA4h*ygtAL{;6P#KxkuM{+;s?UADtjsm5wJg z(9qD((?eh?l#g5NJ^=?b9CLmsJkVna*+Iu6ErB4Beq&5@;hbr6{veyWbpymnim#R< zU>KCd(SHwibhz-sH^Z;qWx?cPTLh)#0>|h{dq}9H{Fn(t1P26ZhPjK8)@uMnh_48r=N#_g9o@ASU9ofLnir ztTAvhBTSC>Uz`Q~G4JD@*qqg}PW@qJ$K~ zD3*sdd@>PpW{cz^j9$3(;)eHvL&}kuWZaQ|@-U3)@b&Sc^Wxcf7PqrS7K1ms>*)#k zhec0-yT2|}jCU2ds3i9_*~v!DjtWd|)^pTbJKUBO46lLc#@ zGy!!yO`-rR=2e(>-uUJAYjzg@}olZn3qQ9xaT0_yEgStpcAR<{~9$kY;vEg z#w`+tvodh?t+dW>^Q5+4z0DVsxII~Ks4&Y>0FR1ED|}lQHcRxje~iYFf{>ZzoaNNg zvW)3boDBpQ$hxbU~y71yT$!G6!YWvH;? zCG4=`)2p)%kgpU|;t4IX%p`BP{xNBeX*dkG=O8)(cSS(vI-rDOPX_o8fyCI_4?U#% zU&f}_9g)%M;$RIrmLajc!3qJSp9#{HB@?P=LHBUKnwg^7r){nQwnS*G8I5>x`j|{K zKGe`}NJ;tTK!Vj#aJHoLJn4P2w*1-`a}&b}G{+2f%hBJKSKBo}Q{MxMywbktzwvOx z5!(QHhg>nV7oWHjDN)KAbOWBIBK}kMF^JNDOeGilQc1m4O?EMcV6w4gE`2kS#=^c2 zvCT@kReN+EUR;UWAZKzB(a@Y*D{m53QDY5o8IVxPa4qnhEVmHpLyM6U3k+kYG_mpt zBMfCZojh$nkx~ENruk4axd^`ih{T;6@)PtVn%;iv_v+|0o49x9=|geJ*LRHIp~4Un z9YKq=?`Kx=INg67TZl`o@tIXYJpFTz4e^`u2PMEi1^}~BwYHh>Yd+?~Do@K_1Pavg^$U*`+zF@Ud_TIvH|6j!WF6z^y44(qx)W)1-5CVV z!_POAhB!Fk9?1x8@FU;RH8OH|Lb(vf5ZDiN@G^l?2>oQXY`R>!9W$-?bIz8VXkEt@ zD+!GGxelFkDnJB^{^Lvc|a3QC^ zEYzn6XSwsGB5MDb7zLC6PhoaR-F}@GygYCSg^LwDI@Tp|BIyki>T>824$gsOa&?qp zgKS{#ADKdQ(Up>Oci)nRpmvr7 zZN9dlws&kYwOyaXUH1$$1@eTUfP=K79G#V8tl*T9JWlwUv8*O@u2zm&PB)sEQewp$P1Q&eTXOm?ss3-V z?2o{lMQu{ML#PNkS_9~6wK>im0B8BE+o`j`892VZJq&O^7d8oaTKkA4WRGTFz=A(l zoVbMd+EK!z+2p;M<@BvXrEbH7=jPOXoGggPf9oPr;|2~b`_;`0E)HbGjSv%Ar>Zix zKuD+pBk^6d9G4Nh5-Gxtk_i`OaS#gSpgH{Qhwi(NkrCoBF_M{?FD+s&h2JA1D_2&` zr=x>_Mv8OypVETq(WXbBNNSR)ejg#n*6Mh^-sQbJ{AE)3i7~sOL>*|IGiWm#H(Tsi z^J(BRestUCP?^Ylm^;y%{FTFJ&s{tYWH#O^)-MD3-jlyFo_M--k!x^s0(9tZe$8@NNt9+JCbL^lWOLNM5W^1qaK)XoK65x{23p=(9k#QPKx_#Q z7nfL3D$569(;wjgIngo13fG#CEq}yXo>cLLuLdILP-CldhRV>`Q1nja?OLVVcN4iz zvrZUjw1s=0?hEtWg6E&4>wm{A4|+w2M0+#q?n zdwE;!LVMp)^E`2m|5Oiu1Nnya{@bsgO2`yRbDgm z3+HIYs2(z=I}NpZY))5arMVO+C2{lZw>DnZIE~Bsj`R!|{YpnA6F(Q@uv(){GOLPVjsM70V}Q$?*ZYjTEBpS!84$|(XEA^L7VAAlhyoa%GhGHamQ^8mz(P4^4hNZgi*7N zn)wc{ZCa!6&8S%33%zBF;=Hv^o#wT)HXwxof>Ac^PnssL-GlfA1Z3g}Z-7W38FPd& zORo2}i%qLDX%FDWB2R1)M7bLr(Z)2pmdfOO|61qmP?a?1H;)Imhb#SW79-h~3V3HB zJ$E@@2V&;V-Iixy#!^Q%BRw2mGr1J@S<+if{*(cixZeB_lV2~v zjIF5%e`P<|LE8_cyh#wr;W8LqOE~y;cPqp3A;5h$5>|*cAZmGAniC>C~od_2I>+B|E5GVthrbzB&6c+vbI2#`oO zA11sPs`aQm1KPeaa;1bi>dM|g_nR(3IjNNE=V9inj-c5o?wF=G7&Q6PT_G}$v zpS@lvG{dL8PV&dG6!jWy$HfZ%)}4;p^IHPZotEEPr)JkvPCE26>dZRF za81JV5Q!%YE46pqLu6o)pbWr%ukf~x@_4moF7=o%pm98yonRI8IKAeR_wDZW_-cPL z@cgB1e0utRhv%A+j_-S%j8VRhU#OIEeuMez zQtp+0W}-2b$GJgaP*pPeb@sm6vPLW^6(=?(F;fZOT!}qfGQU?<=`yCuQ&%XY0Wk3i zk+)|6Z&0TC9-MM46kDpEmKX8*3AuW4n6UF?xHoX&&`2?zwV zCbMWs)u`ym?NUrzBdEwKA0S}Lq64)yQ& z1d_NS1sBgves0U9tn~53<*NQHj8J{!@-!`1_ZZEnFvi|)*0*9wYZ575==Oep zUwb*m-%!Q7F^43@H)!qxo8Y^Hyr(gtB{{oAg-(^$i<94o2EPNd3#V{*}%aMdH=J9YywV zmG@@oEHj*H-xA2KoKYF*ps=vYAa&}ys$3~SB@!#T_uKfy)T8|+PP6KWPH>3CJ3COh=|qjD@kPu& zu!eG#z(X|rJE{E2O`&>)=KFNF_ec@hjL)}iZ(7pehwVKbrJ#csl|Z)(F&`?bm_d>N z!w=4{fI70)UlVm5(6`yH;{0U&{F9;%|hH&yDo2vHy;b*C$>h2+cATv7+P}FtYqabzw4tr@lb7yay^vtD{a`wU^+H zK3L3p>u2&&mWSVyZ8x}CJT2t3t7MoN_C)#f_dA)*V2=7Zm0|lGd=s#=(WA=NytQTa z_YcfzLNe>K?~aG7v%3|#g^zjTs|{=&xP zcv`4=d$$D!##J+tEjC2_8r1}w%7^8q$UANwaTnHUYP#Vpby7;vYZ(}Ydi-J=;^g82 zqZ~>39xl?~FpWtceGr@bSres+`XzB&p-7KaokVW3We@Ic{XFog9V=dBe+gl>w)=2r z8A;fneHg8Tc6$^jPvr6;%qfcxIu54&xVw26iS4INDa0ajB$6x#5m9*k~z0mLl_^U>NgNFD_i&qt%XycwTu?YPoe}@SV>03TWM2Tl3$~u-*VvvOo)Z zCP}%hge^nyD7wF~TK-y^wcAZ3g?!HLM%TRyswzlo74HSA>h0IuA~vsA%X=b${d8A; z(B*UGMzlNyod0dr*N~wPFCdS*M}g{*DGS3=TlqK3*bI-lV@=B*k%y54et!O>3T8gh zG3`LtKh!#4bI+F+p7Dk%agz1H#$T&*Me&oqo%f)#b6FPW@0-p1+_R*+(3SCAk>s5YFd5^*n~m>X%X8^aZ9gzT6c$ zLjdDvo|%9RtRZrf2Ub?-xPUQnyk38g|9Qz$4`& z-<)5laMOBi{JG*}rbdO!eMfPX>Q9NI0c))F5?TZJV#a>uAxm{5O&Y5Fn-t`P~ALBSzX=K!lk&x2M6<^Lz;q+1)w}X_D!1V^S?g zjF0YOq@LFHO%@nB)v8zs)#vntNW=r|Ghd_6dhh=XNkgbIeL`<+to4VQj?~gQzVVex z?jGiZpXYK)`8+zYvbtJy_K>0YVL&@W)7sjbeHtUuX?oNzcecV~jDDIFk}r2=w!Ezc zwdz#5wPx|QL? zgOzk=OO@;$9diJ_IB=__iWz43k&OC4tb-Ju(Yq8)dg@v6g8bE+_(D@1wm$VP0S?d1M!0qXX9q9A-#A01*zt zzH@3aa(loJ{C8{amM}xEjAKa&b5}7&3Z5U|1B*!os&*uDutlfBa4BpBQ*^GMQH7?* zcPb4&@=hq=;~&Q1c{B$QdsTWEKcHqFcV1#-l+N1fX$UhAec`gCXONAl9Y*YAf2YS; zJ=Y2I_igSPcQyn;`!fYf<`q3X;?~RSX;rJ^hqDOY`~qmBWyV~%MunFT2YF<`!VhSG z-(K%}@?_rubK!4_)+a35zoK|RKAl`Mo+EIV?LP4(1(>q7nbS&&E#J%&rd@yb9x6lT z3-!hgyCy>ER%njyHF}61%>a2Kmk-~n-4~6#bA0wQMl95iW|-q1Te&Dwe04lc-W8W@ z!2i8GbZF6D^rJr^pMxm*HC%4uOy=#ta2<-=E`~3hHmj{DKNE32510WZG)?j}nKvKEt%fq2= z!?veX+O^u3$|E653LzvRdl+O-_C1V|VNkXr`<^Y!U}o%VMyMqFG8khpRJJj8W-P;) z@8@~mr_3D6`p(5RbA>*CoC%W@p;Ugx1L@c_{xVc4=>{Q zIiejulZYIuL}t|n)*2uKLRr@r?1-G=<$&8ir^9m7tb_JGhh8+Rb4eLNOgxzux}Bo= zl>8)hl;z>k*B)O29cz7Z3E9_Kda%G8yMQw3?Xds?v+2#uKI42x!ZqNhY4+R)a2tSU zs(pJ#{jz}qcF=rSHhi!KhKXFvHr{aof%#e#HDUEZr4(>%rSm7+y~ji%5jZPN0ONPg zsmG=IPFjzKYNrhzZ>7=PB6RfKkQ<=mP=}XcpO1&%0Goiizzz!2X9An@c`Zw8K6q0e z*&<5;?E{5`1x$zvV{abZ>A&Q1?lLjo(YLv!U+nS<^mJW29nxcUK7<^AWC{N56^~&f z4j@dDW+sP+XVmokT4rM9`xW<>S64kUQbNVmt<(~_?A#`J#~3^@PsV5RqrBa;(hVrj!BOseMN z5gr^5iCdJQ$hPZ`nM9~RixpFt(7B)Oj$pr36=7%R%>+yX0 z%d&o?t9KS62e|t6Tc27R13qRfEr45!_>0QN!G&Pq#ki-r#`#bwD{BO_&aH=~8II|^ zWB2F@OL1^|(^AvXFfTEkc)S}Uy`JSixaX6c_|!%j@=4)!y@i;3tS!45!T>uUn2G?F zXI3w0GpVTPcJtx4B%hm91!O(`J>_Z9P{}5*QY}x)?=LM2Z-n&{wx4`YUOXe1RT)H= z8>kr9)o>K3U+R?G6e7I)*wDhG$4T4y%q3-7dk!aa=lWu?Vh#+&a^#|Sy0}4A?rO1LP*h_uJa!&E7ON383VQ)nWPOAvU{|$I6 zp6%28O#*Bz8FYXZExRDBv>{wQxn)=8bh0%y5Qk)0-MzD-2xF6@eAq7iLigOq0DDbW zexq`&HQ9vxl1@g@`_1Je$XAe)BBKwo21-~=hKP2zdabIjt02gb~E zJVRB$yxwftbX=)jq5ed4*_ReBynLx1s$P3EZjD)Y#6}?RhmBfadpV3N- zVb?GoO|c`~1IGA`6FEzo0(aPO2yH+l;^YLhm`H~52z6c>nOL<~Q$$HO_TglhL@?zd zO(|1a^f594h%p!sAik|0Wv@F)xd(PY3gQwjaW_><5_d0^_kGJdhuj;@M2&>Eo$u{X zj&nUI`1zx;)~kTRrXj*@Bo^_nhgzy+Q6`OoSl+_TqgMoEQ2E2tAHKx$@^ZkL>@KT4 zGU$BF_}}N|V@VKZJ2RFLaLG&)&P5i5p>gsJ)YW7tng#(aIlCU)J&c_pp)-yTq(yz~ zgz{u_Bh^f$qS{AJwT;*00~dX;Uo`F4(shM9H=E0h(UKz8$_NAf26%X0pg7*br_a5C zm@0GD>i3c$N28?ivNV*@NO(MX;~_7DsMf6)g_u)D=3)y8Hu9t+kFN~%bN(&vF@GxxWuA=Z>Z0)pwq2b zvg|uB#knTy*}|6pz?Sf3yl>B3b!{g&RdKVEAAryn0Wnpfn3iY{D%W!fIrX`G%~HR0 zgsx1cWs~$DN`^hXwK48mp|J7uYU{zAI8M%Hm&WC(K=1X5W)&UcGp%;p0NDOx3%M1k z_G>Nl{@#rHM;$JUbK$pfIHU}M=?2f4F0hH6ygsH{@R^{mY73|3WvZHGmqX2FipE@q zV-CTyl@440la4w5y4}NiO8Y}14E)(@0_s*-B%@&$j-{!9Y zoX@5JB}t+2g*G7jy>rbDdG=}kr1G#<OSpfc|*qK=W4TMRd*KkD4fWp>t$-M#wb%L=I+q614 zGP!B;Q$4D_$M6HI0J{ISASaN_ z1|)X)Y`|c2r4By@o4FuEl%vm^uYUbgwB*?|v(4qvxc4j5qlr3d?%6n(n&}C&oSN`R z@!kT;KX0~l8m59KVq}QkTfku`y|%X#!DX*Y_v=}anGEdVsSX+`Ka7s*ohlqnyG{62 zCKGM$*`y+UD{^N(v%G1sh|8me9?Rv~tO^z0|GLO(>fIdp)X0ush8Q^GE<*&kwwua} zBNrN`-VF2jmFP@*M^Z??COp2(jdFRU@Dia*F?e(k>Usg(0)kalqAx1bo_EI2;Y)&> zkJd=yG;oJfH~BJPIgX~OLG1z3nYb7hz;Ze+_+Z9PQmCf7#H_4auDU4j^>2H&C&uiz z3-8m41T#`a>TTGECep0m(EdgjmA^j>FFxXl!iyA zex;7e=T%sVwE&f;ecmtA=Tzh=qD=-dya}`FETa`))?r=5!=BmMl@7*3^ z4qSb1wj>8FhaurwqajhXyv9UVA*!3ud?pcsPH|6&!s1_aOQm?1Rkx%#-__D8`Yu*` zJ%AIPF`r+WmHLa-G%wj%$bQ0J9tSZ*R8%GEq{H3kv;!I|spo>Tm^P+6Ct=*aCc2_I-Hkn?Z)SH%p%oVz(&3psd-dQ~?!O&`5jo^6+31hYd9!T^A-46aYvYQjBY=uyJSD; zqLNB;Nwr_JK`kldH#q~|J|6(j?k|mvrsH#8F=l0&?JI&J$vyV)b7QXyT5?F{TcMQ( zzIH%=5ql^NXYy!^B5q$h6f706LUqjX?H8$NhHVoZ2XYklH#+!~mP%_zOZWcP4(kGd z_JUnqB;T9${Ae-MDXSJUR9Fx}fA3vw&ql(^k|SubpxCdW;pucb=I^g_BPNCqa3?K} zj+uDQo3F3t9pSsmq%>h;o6+UQ4}I=$lNz?VDu=F`5pAjpl6Avk>w<7muZkKH*Iq{> zQCQ=y2N7yNbY$c+>MPUBuZ`2aQHh@A^An*4sATt2&a$9Jw1E?~S4<{%z0Y{UNz`E+ z<$!usZ%TOjf=u0X52~-rTCCK{KoPF9uh`tYY?hs80@QvFqs0|lUmi{-Vy}*Kbhst0 z$UA5W5N~CQ&V6cog&w!eyD4EWJ;zA|))VJ)M$3I~^@7cF=P`;Zrs6(!DsZ`KCi4>~ zP>2*$nP-(a3N7+aKw&&ll3`|?w`y|uj6*+jpMtWZb|TT&UPg9B2Q3Wch*qoX^)nFX ztNc=oRqZFB)N5P=b(-x_WjP5JNU)voOy8WhA%G7k!ZC{1sAyqIUWv&m-N>7ZHCzfm z&4g)rom+~T`+J{^Qf=g74eyi`A6Gaa@dI?389Q>g;gCG z8rUXDeD$mUfoZdQo+E>hOK%r#m^u^;-s~_|oG~iR$=ODU;#g&ePTpS{Yad)ZH~GrX zFZ008_tz*k2Xf#|SiX3GdT>BjIt&eT-BjR(j@bZ+8z6~I@bcPYISOMuH$guxc4ZXC z8@dX>dNrg@&vN50fUko=5YUW(>nMP%m|S8=rQ8$Q6d4^FsR{gW4L*E2)TWo>@N>o zwgZtBtyR0j_zvzsibBKW?vSA#uj1zVLeQR@FwS@0bYX?j%#;)Gx6#CEcaM+Jpadf& zaIjf>&;1ba(NkI&!8^9tMXHMALbg6gNaVsu-|zR!ZSfs$m^Ww45*D(gp}i%oyEP1( zboh?K>w)yDNXysXlf_$K?6C!JMk)&g@22NY`7clbJdW}{ff;Di_;E&$J$QHQ@?Lp; zkNCrdibI;`Q5W$+5i|G&V{7kG=G=n@1`Q|z-<7A4_~%?Pe=uACEns;qaIHzV(etO? z#Yb;|ZyxltPVTn9QvrI9`;bVC!;avpJg;G287nyOxonBW^gwAZ(pUojFW$}E!>zTl z7j+|*XCrx}>+9-#H~u29?QU(W#YZ5^%k&$dAN`$sg_?R#ucf_diF#W|(sI5vg}>W0 z8X}6vu%^6Skr%MN%Nk9)VEPw`dhD{^c64ucz=jtI9yvzRQy@cTUKdp`U&guMe2ut` zk>EmoR*s09hNfWQ6a!@P()dwInbcKSn$hQg(C;+p@X@+XJq(~fc9wyq$8( zZ;pTufgCyLXtv}Yqs0}#YNV} zgQnO=ZclOD)?Qe9@kcu(OTQ`aLw`2y)>Tdd*55HlmKwF?iCL{imw$zf*P{7gyDh=H z6H3k7MN2`uGkip^8L440Sq6a8B^D&P5wF1DdsC?ruP@Rz;tB_t`6#w{WVu<@X1V<8 z;cCOq^jC-P4Tl}mou4A(6qiHOau1XklnR>{Ji)X2dP+M#a}O8!rUG_CgZGpU4}KvR z7*34f)a+P!+AiP)W~~O>{i9xAU*DgrWF7ls>X7v)AfC5A@pvqcwA{i9I!SI;H5U)N zj9re6ny@}qr$tCmM&o*P&s&tWbiB3yeU_<58o=C^Q2P9v-vazCXvKJztk7SQhkCRPs@ z0~q^jyy+Xi!;5p3HooxD*T3=c`2QT6C~rJW*e}w1IPbJqcK|eV#PnIK~@7R3kI*E*u@NxUrk$(Xxjsn(vdDhU^FJ(?@#- zl!m7&50zFMrVZm1-hB5jrUQ{LLBEqnabCS6_stJ?KccA=ERK7ltvGkLkuus%r68mU z#>M8#`)J0YIw7Z@DVWj*c%#rqgU8kYrrvIw(g8yfDK))U+#0+v(-6E%U#lXTXrG%1a=r-$JdFZBrbuCh)C3c z9AMk2+wQaS3I}~eFl|StfYGHZ5Gt$Lxo<7*V% zeEt6L1d;Nc>%NxpPUPCx#ekF}(gjcl%HH_hY%e0?xy>wN;e+F&FEAYYHp8Rm2R=!! zkf-U{jGZ1TnpHH%Z%3hF+GjwadLvHha2K=W8MuDPkzr9Ui1YucuU;?R&};qW@x{)T zU6muSq0;dZ$c8Jl|0dNOA*h!V_$}^mWu2deOBVL?eS$3uhyQxIN{1#r{vMzRdjzig z$S(C{UOiUP?k9T&k1Q-1{EItitW!TSmeYSsnO~hJ@O>>$(BOCei;7OkfloPEWZzH# zjkJ2__K1=#U?wN!ux!K3^T>sg-d3o+k;)oCjB zCd@J5<`f0Z{f5>W#vYs1>-G04LEgXhI(VU^T*hAR(!vbswcQIlzUlXnrt+zL5&gn~ za<`!m1HfykxFTpMrux&t6|&MP1@nrfZmTN#<3d{lmM0x7D1&$33>!jJJ&y(a4oXQ6 z6TYa)?4hPmuc@$D!hb$y&~F6yQdw>t6CLhB&}zo10#D&dcN6f|4Xc2S-} za;dwa{><`uQBIIm=_es7;oGL$o=pk5H_IFI>YpdA*I0Y3#}VDbQ#|n`PRJ4afH9VK*iOhXCVtde==zxX}ZDIM67xC z-BwW+{AYr#3dg%^iy?e3!s{jOqD|}I4b6>4re*R_Mw2@k>YH+$|DxS|xtC z^DXupP{xAE%{`C;$GtdX;X7XHc{(YTg#0FXwV6lJ<;+|S`JQr~Nbz49w`F#Vn%)I( z1?uh%qhY=aBNyb!K*d?KM4;Lk0H2mH^`-MAKAxuex_^zD#`7z9H46IQ;sldRxLh(_ zknxzA#?8xwmt`i(D`NE}4Ol6DtUk#wf zYVK1juQ4DAJD4A@Y3r&R4}V%WXxi^*wg<+MsNE~ZMObMJD%=gQ0ygC^b+ME04hIY1 zjeBdQWJ-<1FB@_tD~WcnH205+3e+@=45zNR^cQ{VHZCe~H)K!F(o}|$2@b#6^Bha` z_VNrhuKScBEPQ0A=go--h&ql&GQ%`j>LXd^$uTJvJyT zOB==2V&Mnq@6jfA>qQg4MQs_f!)il zNcIofYpm$_%FV7sY?2ev&Hk#TsCo6GaZ~#J{c*ecEYAdFhI;F z>cC$GbE7tJS~P7WdcffkWcnk=eY+TM%y31}BAZO)-d9&MEBkC~ z_fm(Z=0JkD48|ef%xS&MV}N{m#iL;?VP*VzuXF@d65pKXR*ONbx>R-ClV_^oPxkGV z7M~UtF^PGqs%W%Y9pix4K7~lmUfSrL`bIhv1=p=YyFhmZOv}m)$mB)mRSl^|L@H!w zVR0lv;#pIp?p`u3>8yyui`8U48>gLaaoOQvi+ghJH7at(cOg5_2e6c6O832qz=HdC z{W>3zeI(CEm+55w;*&4;(rP;hKL7&i>O=QF47RNH)T+S z92<69;9P;AbJr^G>p-P#9L-C$ZL2D9J$@-QWqUa>4)x!=>%)|>_nQKX@#2nh6QkEZ zLR_~vi{nwVTv~+fzya0?7oqDdlv1!5A#sJk4&3x98!J!PIrpX_AbKiT((5RscT4-S zb3N@-H^HH4&{$iBtNIS{g=l9`roCZfj(AV_FEn}Q+6kIo;#;ExI(vY+7-Cms;cXwz!9w(7 zgtByU;w-w+fqzW7-Ua@W_V#ejHXkRk(k3msM5Nvrt3UThnl=)eAkCVi_KZ86DmdLY zYmlFKpvVTZWnV3#6>?bSN?yk4m@sRnt+Oit?(oWYKgFY=P^{O>USDWtu4&G3*-qXP ztrc_-gzVpY2?l|*p&Cz>!GM6wds!F1B*JO{y=j@)|DthfmiDCjUg>T0-nJY56d~aG za#fj!NN)9}rLsWol3V>&*Q?u?$7Ls+E_9!%NPivUv4@4VTkZ9w-RF7h3ax#>>rc^l z7cJ8V6$X{iejly>w;MpZ9ifP;Sm2>q{yrjH{1UZ(wxuiG_#k0xdKW-G)7p?Yr=kB%>m+-y&>vWUVeZmEDtOWHAn+#+N%Vd$?6!fIPsg_yw zev(^?ZtK=Ob3q5Uq}Pus8B0HR5)7MdU0yI$qUhV24olP`Z1p~OS9j3X2Yqz6>`dcfXR1oA6O;9^)eevtS z6r9nliTH~ldndwsOdc`t=*9RYfGCOs2m?D}E50txHX1uRMYJoT*2LrUomt@M_UhV! z^o?(!EK}l3;A)Ktm)E_wTL&H4Fihw^X-+^R#Y0}AXz$({x$z?dA+y%RZEv} z?e*#y)fxdv3<_H&wjczR4^NJY>{h@9Hn~E&KhiWEzs7(Pvsm1g{HbZu&@O zR`-p&ka^3!tCMx#Ky!_jQS4obxVhPJdmb6_Yz@;zXvHL8lR0VN*BFobWSLjSD_AIJ zybApY#oZH`o5gKKL^Z|^_OW5i+bYMu`nUa2=QVCReh2fqBIt$P*`rcCH+G zq`*KQZ>at0!D6)d=&tggv^d4r?I(+;0h@RQx{Xdb8++L(Fi+J4( zg@w9cT8HO?PlkwCtJB{zf;!Ffnt19~^P+Y!MoWZ3RN4)lHpkd~-!159zPHu2q0Tdo z9ry0574dA|JKUIwRoZ#i#h{e6w$68Jt$khj6?*Y-gU&Lwu>YrOM(@o+488)h{1-3= z3?gI{H?Q|}tTxVUpXuQBo2zx@4c=rk(P@Tl&Wb!ys*NsN>^HJ`F6X!DtoLY-bX6&M zJpzp&29v%=wr*vweWeT;rdbC9{x6e$9ilSU{)F)2><9YN-V6n$Oz&+DHa*#;P9VX9 zEGGc;A74Dra78Y(O-uZ1;7Ee4eJ8M0Bs{+#uqYjNLXA!odA^U1fvJ5K!J%LlI~9Sa zA;kW)RjY|*0=_+N_1JK`i5@45cFmo#`wqQtfCrKd=>=Q|dh^dU11m98q1q-wa^_O$ zyd0`g7f-5+8P$~5??SyU&hqp+OUz5yyq}$iU79!RXWs zSHNjEW$Hci@+J!@r9jMILXFOKxzwAt8&xBpptIe#N@Fcd3^BzwCy3xwqKvzu%jBQF z7@c#hqRIUEOgD7+_>XqU;OUUc`?T@+GgoiJTRiM9$>}|UPLBvJC1;zwZ6HzuLGLlcAR*u?m*GsYI{68mB}X^HM*QAQCcGJ^HA1+rB{@ z^Y;WGw0@<9k9A9<~0o9D#2gLnUUe%+n?(DBiq5gNxDB)ve^x}E#T z<^UjtNCp3SrzM^xxktIm!Xf(?fWT>hOJ6W$COBY+*pMb>zR}7UP&)Ni=Fjfcg~08N zN&jf~22NsPk#2D)sl4VP_MS4GwQ_#m0lABHe+rY!Qq8V7`MAyxv&b=V?_9(h?1*<( zGoU2sj_o0i$YEtjYT{05)Sb6iN*YGB7I=^pJOT?qBUX79)xY=@$`MF^Y`$txiC6?# zN%ClQDlY(q9&H5>@YpwHPt6S==Q#!+4hB>_^6P4JEH$kPBvZ5;S~e&N>fF$Sdr7On zNsChoq$UcPbjgyFxBA8A%OZKd%oK4|m=w>E-D>Ynz&5KQk8r_*_FP8S#C_|aLu5zr z>u)=U^pLe9!OR?+wJe#m;h633!k#=IO#8kFoG0(u&e74)h1>v#pXpT%(?Rs;fk%z@ zZBhFCEC*=}dIe8cYDe(K*w=yi7V35{z}vEx=52myuKSTL$Qw+_ma)!}*{@OOZDI7w zdnj(Fe^BR1 z;fxn`F4!HG-6??m_HgA>Phrzly@$^Ij(ovIOgEH44-U?s4;hiUMm!LRDPLjVdlzVa zL@jbI&N;wFuC;88M|-v&lAG4P-t(D9dw$&`XL>%%Xsk$Xmv?%e%H_ndb9-`{0Ymj~zXE|1BQ$lR%`ftGA^+%skDz2K$hXqo)ugj$Ut+sc6a& zxvE=OcAu0&8}kYN5Pxz0{ntYmXN7%6RX!=C7z@D z_*W&&is&Uyi<(!ykyg@_7>UwrqEzra>8bQpnl2*Um;SZ)jfZJ-NslPF2fm5?a8*}v zhT@|uJ6C&OSDq*gK`$I*E;3UWIPL&xfBMCaFc~f0_?MaaV4rDcGGSH23TU>$eDGVH zOHyGJVgP~1zW%rxeTLF`T*&EN_0+rL9yW}rf`^$!vJ zj8Ue~e2m#|T%KPyqB@OC^qR<@mI^IoS3aSOtQzBz(fd8$XgKVf@~j}BMwks>1t_@y zOmsJ$k#Rm7bOh9Yiv_Y2CmMG(FG$LbaP5?W!!+4Si7k!ZWEiE94XD4gi z>0uo(uk+rk7SJ*gHNUGB-5BfL(lCf0%YI?hTj$us^{5HmP~*S1iQsCrxC%4AH{E2n zaR6|mPBS%B)Rcic(;NE}v4)+sj<9j*3TxA91Mge#zaIn#GWNeu%ARv;GEm|i;?077v#YdA5j5ftCScLWHygNbYd=3>Q~@m`$9y(?xIhybS0$+2XzA$G!r%XaU<~eTjtf)HAkD5Tna$JTwRFh zu>GKCr2n=Z#xq%43;gL+a)|&jVa0`-J@o5>Yn`)53q360w&v_|YoW?w`OK**nrF)y(?Q^uM&h&c;2vm(r)gbxq0FFw23C0>c6^ zEeVu!K(V<)My}aH@&P8xBGTkpxN(DPok_35fQE68yAirwv<{i(l9mKjT;y=>Sf)e~ zaZendFK$PjsTkz7lW)s+lh7Ca#}ZwfyjbtUK}d7-1#gvAOW|GWV$@py88*gie^I5z z3L4!@(!B|kkzM8jO`H&O{ILQe{}kRbb-q{sSUk_k$2k}co%1`|vrADr zph6KzZjSf|CYod{ku3U>EVUjBKr^k)gPN$x=S2?cFQF^^s)s*E)*d~Fj|vE^EEw^mg}`gZlZWF4%IEhMuaCDG z3{_o0yb5ScE=`qtlp!?%Zo*miA>O?j7xBfYHF#1{X^!Ubw^+39qE<6mVDhAC-hdEf zzg@Q?{kfs3YS0}(T!>G09)ar`hNxZa=sV`n)SvS8Ri^y| zTF^u1p|D1u={>j6N5?<^(Nv(y($v795o?n|v+8c(rN_)0^m8jK5zF5?;ZojKhjA;= zY7cf;P}DDM^SC$1Do<{&N*ubQbm%R1Ibv?UNN1*lPtqLe_;$1JqOnnt+ypFbpHCnt_JstBg zNh1tVn5NZK{VpqtD_dD;Zr1!sjZTzjJ^EEiG$abKipm!Iq=AeyHkeZO%6lib=kXnLv zp+PMwR0~YP+L?3B!d;TX*mC)iY}W|4JpMES_bDofWXJU50 zS11WGXX04&s7B<@wm(_0Fes|6^;smM)6+aCmwZ-gZ&)J#pV~2JhjEqGvB58Zl&+(p zwzfA@MlV9||4fY>=R=_LvW!gN>Yj5`zS!f_X9*IXfNDH`$V|Myja_59LdHbc#XrYp|wO zXG6I8XV~Gf*qL-Jjyn|-(pfiNpjlQ@Gk0h(t^YiJ$aK0i3yjWrai*@NyX@0u<0;ul zMX#Z-0mbb5e%stl`LQ921+mlq)D~`##sV|*W63P#V}qeAeeqE#?$~=#ntIxMRmkX& z?U-Bedhi!y`E!xm`GwG`AAi10brOLcuvSfrQG^=T$Nft zD&PotAmR1*C%u*7S)dlDup56*N#V`%cYYS0dq7)mHE!tOdw}H2t?=q6mR~J4Mbi5z z_@XS3cw+h0xfQ)(=#dQx9!_orj-zdRGrc{Tvi}{Bu4=PXbeY zyGk$FjB@`WhZg{$EYo8-i1)TZWci^g%Sf;5tOr zEb9=~T%y-St47|8w#sXDqyPSlJ9RDPw3~JP%23G4iCBu>ufIr5{SmEmN2|-OrRU-Jm9e4mc3QBUE=@K?S z(w0WUtOT-M?k<>jH%1uLW5xc_B*eOu*O)ma{GF8pcQuSPs>x@T%#MS*WK0Uh1g6>R zYL`pETPwOAnYTm@TqckyWlIx;`eUjN!K%pM%1@p+L<^5#5~QZOAQcFjx(%r{eR+=A zkiS100IKH^U%+Q^m$+wCob+xw4Ad#EHlQC{PDd^X?LYo+o+_jNTh`Y<&Ip!(I;ENo ziaqNij02nmRsxHywdK{UDdqWL<|M;*moy)6>);Hh!K|GWk`qyG^{tl36${pn3rxMz z-0Pb8${gj^5LDKgue^lzdyAATsWPT1y_m=zx4kKVctssMS@+I;`J?y)wzqBX~gl`_j%F`@S^U6{2PP!lj1B`9wVHf%A_e z)OE2qxBK#-{0y-ftL-~m@_mQ($DsS8!Pl99syV49$&Czz-kuCHd2{% zGC=G?Ld}lZ?5yfA5h__Rl;|`=GcRX;jKS_unNpw}%>;K5;j*#VZiovmnOs@WX zs7Zg4`NyIV-6jeshWreJ*W+tDr{=<}a);G9)MIjDJ~iC4n~As6Ap{`|5@Tw~YwA{w~WPHg2Kb4}+wo#|M*4hoo&K0-EY%@Q7Dl|A3GFyO!bmq$! z^i*8CAXv(*NX?^v;uyqV2ywvTL82gLE=Te6Qn56HdwWNDCn z-|M^7NED@EB=Pgj<2QV{MtIMK zlMLX&I*{-mEneZ_t0+t?vLpx8jAy!$XO66F$=slLmp6clJ~o(Z-q9x5N^8{or?D2dsBe!GGpvELyl zlIyh<1`@CumZx0g1@LtR+htX%&DB0IR|6;~2xrAq(L%|;7;-A|ZX1S_>l*w&uRa+y zP*^rWA9RgO`MC^<=83%+s_h$#vSRYA!;7@lKXfoJJ0A)v$(b23x4;`O>eXfS*jM{s zXEJif1JaJ_XHhd?L}vqk)_e5Ggb212&2~niiNF1oK2#3q^it=F*JS>BC(jFBVUe=3 zD}Tp2W%coh0L}-QtZB?nOVRAA{-}vd`~_V8CKRLNpZEC!Kl6f+xlRhF#v9K5_n5cl zI(7-F){nR6Joz}&7c@I}#YXw$T)wakp5A$?4)A zoYVhybH5_xebggb&kZIvc+6k_*udQpLp`bUDsrC#Hl;31%|j7to)&6C1vM+0I5Bdw zr|S|;eghcAF%u9X-+FO$QBj{#QM=s%$PeHZ0{@Ms;sfZkydg`7wEOranreq8u*G|! zRQBvc^KayZ5-Y}*W`qwi_vKm{s3a&#EamO7_`ze(#nKYC_WCkq`T=$6_jf0^7R0kE z0_)v5>;{K!c7OHHC3ge=NTw2MluUts2MsOMGQdzncbiIZ^s!(3mJ%sE{`psSa`@If zQvdFY%4dr8FsciObJdI5?8F6GNvICrF^)GY)7dnaWAQK=DCFByh@+0uvU`##eF}Sff2lQ z=iBlFUM%_Fvp281Prd^p-$6{wK7|d1eeiMU19-((C(2J?nfzST?eN%%WM>7Cfsopz zPelYAQKX0UZdSl1z`OC;PVO@7SJcy1bljZna?JHP1RB5qs9|(bmM^w3Nj!cYV6X&q zpIpcen37%4Rs`sjh(8A<&~1Wb^UF=yg?SLx3PWPLRLVF+w;zDmoi}7a!Xm~#J-RQ z4wuDQ#VxL#B2PU~IH29Tj5!ohb-j2#1gt~n3KKxzsW}3dU;iqwAti4LYbR8UHDI!z z2;cGT+G+8;mVIndsr=QCd*eI>DnaFv@dow$=QYbj&40zoqZ%8%faX{&XIXGwM@T}* z%25_`>cnHNcc0z3kT>x2>Ym4kmr92;o4b8$q!OsypK&z0`JnqvhI00Mo2b@dQvXN5kcubDX$}<5M7SE=YdH{7a zCj%@%Ut$F>?fe4?P$F^$TrgT`my(Sl|Dpp{FVl3Eh&;ny-xCP2)-fd0jlFWF0>|7t zLpB4;L-*p(Rz_tjR|(!loU@EU1P=Lq{1tJbgZF-UW7QGO&HLg1$@v|cKqWmr_~V~H zf35&&sx^eHXdR1BhMlrwYd90~q^4jJFU*t+GJx;pYp^IpP=tE9KPTAmcH%8J~W;~-QWq=%nJ5mHt>?%0{{CtgT- zVxot-IXrw9QGu?iv4vxNd;+#GceRwsJWJ@pG9tdhBqg&^coOXthaqV z$r|p9^JeU+2VINE?E= zyT6m-w)O%QYIZRl{x3m=_is2;EC+lh1EmFlI>@7SprKOaTld0=d-$oHYDZ@8)g2vuVK?Z*c} z-E;*%Kt15Yt*(%##usCW4$3e8n|la))@)1k6Gi_cxH(*8so*oO?YyMv?&j)JtCcZ$ z<`ZYbKbf2d&lUK`h37RLB!?%`wF6CssI?7lo!h>~Wq~(|L_7f17%JCwkR}Z7CEC}Z zn|-s_=A#=g-p?j!xb;&Xm=o$D>Y*mfeGvBzqJ?3bOVbd37&l04Y?m}xoN_4s)L%TY z*(@5~G8*WpLRz?w?8{3^$W_+3XjxV40(}vl+ERX-KeOaol2J{M2x5!ahtRGza4Ji8 zcaY1_^+=@p5Z#SLGF_&#RX0N_ zk(-%qn;{jSWeY?q?CNQ+i+`2z8P48!%Vd{9WS#Jp5vD&j@ zC+7S95-QFKUbRD>fXb%0oeOo5%|y)y{HWxHK_Oel&uhKOOyqWRU3St=#lFEA6@smR zaO^^AK|tN4ch~yy397KNlloGLYL2y6wgDntx~3VBjC|~OQ-l72)pNkAFSi;<+es%m zi<{J7zi{*8c~<%v(Mt3JmgxW;jMp9_O<`l!7N~#Sy>`}RWP{;<@L0yNhLExeP7nu~XPwL{p*45A94SnT6}AM|OTL6Y zWHV7XfiZ9H?e^_ed@efL&O(_*1nyAu6a{1WA-a7iY@;Lh;YQ)2iFJaTCoPw;D^r~H zXk&bFcq(A2k1fZa7O`kJhhEZfzqddBqwWF)({G(Gv?XG$4I^EBTOLjA!zQdSO zUmLKff=mG}gKvg}1S?EyX4RPwrcdU=AMVdI+~y4i`=RE{8S8j-s;J^j^3^!r;E^Ix z04}fDUOI+D`WZ6f7PtW^6D7#LuvcoM>h3H7CHJi3jiWFNxC+7UFUO zwr8)#^07tqx3d9qx<#Oi%2!KQ#MpbF8c8l*+qZhhfu--j@^eu=?!Ion97$oMEGWV_dzN9}_|5sym zUMKgl$3CkB_52N`3SI_xx}|cSVY7ne_3PPRjMKyn_#VplMms`f)$|y5FutH`VTZh@ zL-NazOUAUeb1BDE<=1eoKI?R5EBe&x6!T z`Mbqt<@HBDGOnLe)Us8<;Eq_WX$>UHGxjIcd0>Ts^>z;KH61mQcrxobD zx%PGR%l6jvei@xNVEsFT(zIXaMyPZl{5ALZvaJr#qCIe5TMw67ldb6chtpP#-Yp-g zKohp$zsL@Nz?=nitoqjK0QyH=&-w(I@h(c0%3AsBm#AZIKtBgi-rM{jm<`(mA_Htw zLr^CXIie@KG6K)ff^9k%XF;kd)=>~Em*On%M9v>Vf*Nx`g*GL%gBSj0F3KKgm`Ocu z-D;#0t3rSNZ`K1mKfY541=8y`2ZNsbe&kh}HVRPHZ|N|?YJhvK@MNzj;mhoYs?^ct zl&WVN+BpB#K~a8Ac2M?1=O2@@HS_&BGhn{}(5!k~CH7wY0+P)&MTegf>RO(?O?It_ zY}vue5Agbp-1KaP46H3TOr!Obynks3Dy-K8FxGb%zX4e?<4@~aE6`knK~-WXQnIzg z9y0!J3G%_mNRex;A{5l#>ipDpgv9H^IqSpiFEeq9Zp(RbeP7mA&5M){zJ>C&>`yW1 z#jR&T$$)nng}4T1Bx?wUY4 zs!B)fd8Xi%H0%G@-gW*pm9*`kf;=whTIe7s%hIKT1S!%Kf`FkK2vM3ym9BIakrkv$ zg7g|%AV6pmB(N$jMI@9EDG>`b5ELaSLExFN`+Rx-h1aj=mynNBJHLr5WJOV$8n%1iq`9)r7jUZerE_c~EMQy1``j*NOhIlo6#S2N_+q zs_51jxHyN>E<)99J?(0(@94N{4ujbQyV<1JZej`8R_w)O+q`^Dmard<~`1#0W$(klk6_tZDNy~dhEgSX}a_DYO#?$j($U3x}XFPIkMy0dl`B^0%Gk| zVHm2BaU=ooAm#c?B(Q37v>o(&_z%Nx8B*%1MU?kBlvLl^-ph|C?<;d#6#$I5;M?Ns z-4+D?Ua9;gsUxy6g$1r&K^bUAOiC3|B%=~`T{Ce< zz8OljROA~Xk&5JBjN`O?5qrxsRz<)-f-Kv;&XTvOatVFkD=-bWg9#QJvM8UmucFrud18$ch=)8DMGqiZ<>yD)t(??a_z)IS?|Qtx_T1;^X{S|O$NwWOT2&;% z;FmLnA4Qg=WqpKVWn$pH_UVafqt%TCgIM=Opje^<_W%SjB!|<$C^@Zis`9lly*Usr zo_V^QbU+3p$<|MN_5sDf+ctp$s~hJtXy{@QY#)ZeNL{^4Ua}#zGm2xAqcj5PF=N$lwqV((i4o9j5CYo#{tYSqvY-Ag!b5Wk}GYNh-X*D&k~5V5<=_c~`TL z>IKO{tS}<3%awWgSkQ9X+t;RQ)T@0hRh{`d2&zL$7h}lhN^@0Jb8G8Xig$;DX#_~6 z^!J{P!7vIV?Q#9pD`QBncb>FAKjEt8ma^T8E0fCmZ@W9Qf~19fks&1L^>C+=oibhbzOE%|aRX2^Tr6L&C%X+A_6K z6Yz8wA$YIr0bsDFBbUPDbA4~Rb&Rgqck(uu+pa1H_eTZ5mV{}FE6-~)Eqw!1a?SD6 zuQc;=Ck#=zOn+5X-n0>$*OfuE{N}EDkZfd_+19OjYaf4-8&%oc7a;4zpum$deX?Gb zjUeRO@=akQSqYC6Hq;Z*7TP%*b$XXr*Fzi#{k0T+C~ZCpCQGG1E{v&Bnx?Ee-jW}? zkq@YdIWc6o?4xYchHS;TspLf4C67)hbx=mBb2S<4!%_WZy`4&stomi%+JnMkj=E08 zYh&Zw+kc;pE%nd#TYh5SPNlMy=a5tgcLMK)NN{e=J0JJ~GqhsVVA}>a#rG?Qa?QJF zFMfOF(*4*T?b_h5dfL&@=MG=PxVHOxN-rJv-%kD^3Bix0v0L`it1HrW0uA6yHnskD~3q*3%6e;R7n;12(1qH; znk+oFO=7HZ=GI{4021wL7^69=1)KNT#pWQHGr{TSP){QpAX7!F&t`cYf*E%sPBA6< z(=YC;^y@)Bn;Ku0U3MB$@6`S(m=M+~U+g2}E|TgDlPzu}_|20hs?~EM^0_ds#Ji4s z%kML!X4^ZQy>j9mP0O!(y#J-xMi^qp}q<1xKt6VIFq=q@kuMnrjjOHL(6V(;qMqJBE=PXPW^^K?E6e$Rypo zz*vL2?9@e~FdqATIbQV!A=`pmba6t4l}?|lKr&BNXf|Yr)ZIgz$umzuTJ%+p8)YZc zMh{=pm-$wkD&n1Rv1$0V^z*U^QkGi_PO!S*i-O%BuIX+e)xW%`t5!2@w}B(kp|4gw zg_h?XEFw2MgyaRNc*y8hFJAANQwKgO!Ku8M)?Th_dh86*#R560e%mWz--(b1sOQg& zTgfsNuY+$j!$Lz27kz@-G53AIkAN^?c;KifmObq9g4&Dl_n?JU#HkMM>ZE3 z)?Se+qWNeT{S$bd}XUHV)9zknAY?`oQW=e z+PpfaBscovnH7Plaku3Wh=X8Rhsyf`?1!4Qd4P3U&0br}dflA~V8x8N*l-;@f&3O)VHc^F>?tMtFF?%=iqIL7qb zXqnFAkB+O{-iFUoDWZ`yj6kKlNfW+|E5D$SGKl9gfhdB*YDI8=Cs%0b-E%2;IelcG@k)LXTN&Q`# zdPBg8QD+{zYFL&R5L#DJ=+|WDK4!0RqHU!1+*_6QH>S-*NITqQ(1Ja)m0w z^OA&%Rb;y%Z0)4~tnWnly=8}vyNO42LmwX1YCx>|=KIblwm?lIrKn|LiXvJFyG*1Y z!)`K9cH7$~a=87{bU=gg3T|kjbNmv)k02}GZ5DGq#|6sh5LiQX-5xppCHqtt+ zB}X^l-Je{%-ed*#n#FxUf~PdbeA(oaNb=MS{`Ct2_7~x{y1g$cXpxEfw=HYIvM9V$ zvsDhS54F!xv(Bj_1~{Yp>R@z27drSNbtA`o?44gs0gO>J|4&`OxrRA3MQXs=KQ-%0uO@ zKA5m&EE{O@@joNiDf0T-d5efTw@vQ$5Qa!$jz6jol`bcBjl`(AkR>Gx^>&Cii=w?I z%eH{IW6W<*3Hs-?sf7Jaj?LUP3X(fFH|KoQKtwZY>!pa=&Ec)3v5w+bMa*?+@!0d* z<>^>e_gsxv10`#`3IKdo4}bl4@M4sD+sv1M1Grlh3D}w`z&2j-M-*kW-eaBs$h`H}y5Iv-c7sU@s5)mk z6AJJk>I`bN=yvR|7+93YTz-H#x9!@Syh|`wqLFxJ7PJ%znI7PS3_m^q?f|C21@Ol) zS9ql@FD#=MV>`S@{s@68{%!vfsUvg9fc@SYBLNK7$P&7v%dED?#q52IG49g+y^5}? zsd0htFJq5<9>XM*T<)Z1)psVZ7^J^W00T=?e;fL(o~_gcg?IoPQ~?(w+18+eTX0Kl zW(d;q0<*<$P?lH_GF7uPzY%ZJpjqOv1nlN}Fo)4oX>Ia4h!E59Ej24$#=}R$=a&Qa zb_3q-y-OWlh^lCSBy+jwv>jYww=E8t2#}kK;pZN;2PE|C?8R!91msNG@7L5BPkmcH zYxGbqIieiEDQOPp9Ln3flT=&%oH$Q(p2-qag=B?|$qbfaUgGd1-{n#4)z6jwh9bnn z5r2JFlDih{{`le&G(L73slVNOQXkdo>VZNPiv|ll5slTAU_ZKN%>Z)GZ~ReOgvsA$ ziFm7VW*H0D`Yq5q?o0YZlXRFd1MlbHOr2084q+d9(FE^aMcUbaF{+=;B@S#t8hwzrN_9qE3JsSD1ZSmf|?q|XEZow(0u zapqc>Mjswgp(gD+jy39lw<7{w&ZyBllap*2>pZ}J0nJO_hVm?y$>l8e@YjJOQo#}XR7?c5`VTF2N9DSQwiv9w9q z-d1ASsAR87C-#n~RqXNG0im`##c*KF1;N?{C z4>+W>pTeGLwS{RYcN&&U%Y<+xRyfcVJoDXG1yFalezDSR4atq*eX1wS?q(b<8N&TW zhjb0n!oV6l$1k1c^6V-~2G}{Lwe@9|$pH!WuioeYtIV#CB8Lo^1s;zD#2{u5@DR8V zxdgJ5B_9H*@aGu2w6ASybUF^kn{VI)YYsHzB9K*fg;o!Ur3U~@2 zw_CE#;9#F7d)7=KhgQW<8lW0tUfK$7{tD0$XB*_MTRt6moqmQ4KzD@Mzm1Y-H%b!2 zLGC$0JAfH)h!1&60q}P_bW^Zj6}eCcLZc^Q~zMK3l~=LInKC2Iygn{;OM8g zW2yH7{vprwxEQe?t3y1{w}<8+$1QU3E|wK@%!xcG$>2#n%9Qc?_lYp}6D7XHog$!BUKN9Gj0lnAAbrf|*8%EA zQv~G!dupTnPIPct&CM{*#Mg}_MTbs3u0cRTw$Q_RJ2Rubplj?NTptD|u$%no-6cqM zHy8or{ z${&XLXqv*uuGKHN;=7vyfT{iu2QN-H0laXh``mG&R+Pcsz z)Agn|UP=X6slQu9TuuK&lVVg-{HIUDFT;IMeLn?)zj;5ra(v=64S9U!N zI$)XtsYrQuL}6SCz(u}Dm;IQLiFhee$O<&XvlDIZRW6b9LxYgYNp7*xt?r%@?dqV;iT)k3FM# z*4TRFRMORDFFzsMscBm?_4f954f9)GjyY>9+JPU;j_C*O)ly)g5Zmq_myp~qum
    f|vDE#rc zk7;_&10x?XZqSK`Z;!%lha zb%+kYCM&YS!dbgYxf5y<&m`C_e>2aX!?~WIiaMX>HqAFJiservice-defined operations that MUST NOT have observable side effects and MUST return a single instance or collection of instances of any type. - -Functions use HTTP GET method. - -### Example - -```XML - - - - -``` - -## Actions - -Actions are defined as: ->service-defined operations that MAY have observable side effects and MAY return a single instance or a collection of instances of any type. - -Actions use HTTP POST method. - -### Example - -```XML - - - - - -``` - -## Bound vs Unbound - -MS Graph does NOT support unbound actions or functions. - -Bound actions and functions are invoked on resources matching the type of the binding parameter. The binding parameter can be of any type, and it MAY be Nullable. For MS Graph, actions and functions must have the `isBound="true"` attribute. The first parameter is the binding parameter. - -## Overloads - -Both actions and functions support overloading, meaning a schema may contain multiple actions or functions with the same name. - -## Parameters - -As Graph only supports bound actions and functions, all must have at least one parameter where the first is the binding parameter. The MUSTS of parameters: - -- Each parameter must have a simple identifier name. -- The parameter name must be unique within the overload. -- The parameter must specify a type. - -Overloaded functions MUST have the same return type, a unique set of parameter names and a unique ordered set of parameter types. - -### Optional Parameters - -Graph supports the use of optional parameters. The optional parameter annotation can be used instead of creating function or action overloads when unnecessary. - -Example: -The `getNumber` function has an optional parameter `date` . You can use an optional parameter with this annotation: - -```XML - - - - - - - -``` - -instead of using an overload like this: - -```XML - - - - - - - - - - -``` \ No newline at end of file diff --git a/graph/Modelling with Flat Bag Pattern.md b/graph/Modelling with Flat Bag Pattern.md deleted file mode 100644 index 136f0f1e..00000000 --- a/graph/Modelling with Flat Bag Pattern.md +++ /dev/null @@ -1,131 +0,0 @@ -# Type Hierarchy - -Microsoft Graph API Design Pattern - -  - -*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - -## Context - -Let’s assume you need to model an API to manage groups in an -organization, where employees can create groups and become owners of the -group by default. At the same time to support business processes some -groups may be created automatically by daemon applications using a -service principal account. In this case the service principle will -become the group owner. People and service principles have some common -and some unique properties such as both have unique identifiers and -credentials, but users will have additional properties such as email and -manager for example. Conversely a service principle won’t have a manager -assigned but may have an associated application identifier and a -description. - -Additional requirement for the API model is to be flexible and -accommodate future needs like a device may become the owner of a group. - -## Problem - -How to model group owners to allow a heterogeneous collection of users -and principles so that API design can reflect common and relevant -properties for each subset of owners and accommodate new owner types in -future? - -## Solution --------- - -OData allows us to design collections of entities (entity sets, multi -valued navigation properties) with values of different types using -**type hierarchy**, where there is one abstract base type with a few -common properties and one sub-type for each variant of the entity. In -the current version of Microsoft Graph there are many collections of -items that represent slightly different things, variants of one concept. - -## Issues and Considerations -------------------------- - -When introducing a new subtype, you need to ensure that the new subtype -doesn't change the semantic of the type hierarchy with it's implicit -constraints. - -There are a **few potential risks** for client applications when new -sub-types are introduced: - -- De-serialization code might break because of missing -properties in returned collection items. Even though property X was -mandatory on all subtypes previously returned, the new subtype might not -have this property and the client code needs to deal with that. - -- Client libraries for strongly typed language might ignore some -of the values in the @odata.type property without further configuration -and need to be configured to be able to pick the right (client) type to -deserialize into. - -To minimize impact on clients type hierarchy can be refined by -annotating the collections with OData derived type constraints (see -validation vocabulary). This annotation restricts the values to certain -sub-trees of an inheritance hierarchy. It makes it very explicit that -the collection only contains elements of some of the subtypes and helps -to not return objects of a type that is semantically not suitable. In -addition, you can follow some of the mitigation techniques such as: - -- Avoid overgeneralized base types - -- Think about roll-out sequence - - Consider that Microsoft Graph does not return objects from a workload -that has a type that is not configured in current metadata. To avoid -inconsistencies, follow a two-step process: - - Introduce the entity type to the Graph metadata but don’t -return objects of the type in any of the heterogeneous collections. - - Enable your workload to return objects of the new type as items -of collection. - - -- Allow time for testing - - Inform the clients about the change and allow them to test the -changes in beta. Time is required to implement the code necessary to -deal with the new entity type, both in terms of de-serialization as well -as integrating it into the rest of the application. - -- Communicate the change in semantics - - - It is necessary for the client developers to incorporate the new -semantic into their application/service, even if the change is perceived -to be small. This requires early communication and clear documentation -of what the new type represents and why/how it is considered a subtype -of the original abstract type of the collection. - -## When to Use this Pattern ------------------------- - -The Type hierarchy pattern is well familiar to OOP developers and well -suited for strongly typed client programming languages. - -There are related patterns to consider such as -[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) -and [Flat bag of -properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). - -## Example -------- - -GET -[https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners](https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners)  -returns a collection where each element can be a user or a service -principal, and has an additional property @odata.type to show subtype -for each variant: -``` -{ -    "@odata.context": -"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", -    "value": [ -        { -            "@**odata.type**": "\#**microsoft.graph.user**", -            "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", -            "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" -            // ... -        } -    ] -} -``` diff --git a/graph/WebHooks-architecture.svg b/graph/WebHooks-architecture.svg deleted file mode 100644 index 1f690100..00000000 --- a/graph/WebHooks-architecture.svg +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - Page-1 - - - - Rectangle - Customer web service - - - - - - - - - - Customer web - service - - - - Rectangle.2 - Aggregator Service (graph.microsoft.com) - - - - - - - - - - Aggregator - - Service - - - (graph.microsoft.c - om) - - - - Rectangle.3 - Subscription Service - - - - - - - - - - Subscription - Service - - - - Rectangle.4 - Workload Service - - - - - - - - - - Workload Service - - - - Rectangle.5 - Publisher Service - - - - - - - - - - Publisher Service - - - - Dynamic connector.1000 - Public subscription - - - - - - - - Public subscription - - - - Dynamic connector.1001 - Public Subscription - - - - - - - - Public Subscription - - - - Dynamic connector.1002 - Workload subscription - - - - - - - - Workload subscription - - - - Dynamic connector.1003 - Emits Notifications - - - - - - - - Emits Notifications - - - - Dynamic connector.1004 - Batches and delivers notifications - - - - - - - - Batches and delivers notifications - - - - Dynamic connector.1005 - Validates subscription notification URL - - - - - - - - Validates subscription notification URL - - - - Dynamic connector.1006 - Gets subscription information - - - - - - - - Gets subscription information - - - - \ No newline at end of file diff --git a/graph/WebHooks-vs.png b/graph/WebHooks-vs.png deleted file mode 100644 index a844bc0979356310e3d5a0eec2912612c25cbaa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64391 zcmX`S16XG7_dT3#+qP@6J$bTi+fAN0*~VnsuE{mow(a+PzW?j@c3pLPo~O>ax$m{t zT02rnK?)HL4-Nzb1W`s>Tm=LKbQ}0R2L=rIX?CJ80$e~{RHQ^fs;3A}fEN%JBHu+o zK&H*+Q7 zU}9lnn=0tkp90f++^Bf zm3ZSawIAiD^7~fyLa4OVVBNt6o=fu1Qn@9 z|ZrC~T*tyFPaLMQ{I)un6GkC~^ zh0uA6z+P3SqCrz#i+69txYooQrn;A7z%tWOWb@|h_$#0pvGSo-3{ei6`yutS1g0i9 zj4J3?==>9nG4l3h+`_LVK+;H^t78-jakZXpIDm$B)w~l!vp*t*UAm48;k>gsfw~`; z=FaOlprjN<(PBxm5m6C;ZqtKhx|V|>@*b7B((G{I_eo;m%wZH#7Y;v*P3T)IA=xcKT*2raElsXgj#a1E2# z)=P?Oxw5q97^zqK=SfO#mUye4+7qtKP4T(^?%nW(FkI3z|gj zpK(PWBx`452nv-<6t?>Q7IiBGdVjf7t_P$wU2^D+tOKsl7T@DT*1V*7t!OAJ)3>Bc z@p=qkRrgkIO(0#jZC-}2^_!6gg^rq87mH@*l$?vs z9n^DBd<=!^V*F7*d1yy)2_sY)IJOrKr;xw6((BJ4PH^NymZ3%r1=bM$iQ3*1@ML}+ z|NFqtuvzA{pdHHa{WXCZMj`gdZDJG$!1%)w+Lt(5qvI|4o9Gm_J>F>y<0 zk0+?2*z~ZjJ<(0}TaoAy_!nFH1SF)yA8O14N7NH{pC5FH2hdUEaN>5>rB;pqj^OXFgyP#_N{h!V z8SJ=m3Q_JU0>fw|NnMe=RRlrjQd3e~O7>S*z2PW?^)RHFrZ9dYMgLru zi|j&*;x^))Jeri1m$v)kK$PU_ZNqA>n>nOxCbcuJq@ahHSx_(OZQRTx5*G zv5g;$zA;J%#(U0sH8dV!Xr{co`?oVnxP8yk&P|7}zeC`Ps2=2f+=9A_a^%#HADjp@8=>B_Qct>-;z|#4PwV`F7>k z=rpkSNJ{>A#rM9D+Vgqc^(57Z%s4u>(e5-q)Nm9Z#Q0&a*YUX`Mf~-sVUWY;=KX%m zD$HNvP^4X^A_zGHmNcO}6CGppg}>wtXMYb4^ zw+6V&$CHrf+l5C8qYh}ALN@RG;^LIra6#bGV%6H&DPFVf3YW{_IlINw$Eilo=i1h1 zXm70%`RH>WG;!C<;Q+Z*EGA~k!2uRyTF?GrOoxM@XQS;&xs|F#Hm}#^%^UI8yusJN zgH4Ntd~So;UF=H3;XEwK+hC+>Rh8IqG_KUbRrlNH-UzX}#*(_a`qv@1!A95Ha439g ziF|q(XcQ!PNd%WWR;#>Kqw8zqN+!1*bO6V<0aNnvZa?TvpR1d2V*gIZ9pmG$K2?$( zxzy@FSmWBcx2LU6|Bpkyt4@!{^OuyAPrq03dV54-LFcc}V27@UN8-=xxIf%}uM@E7 zR07`jOpF5l?-b@jUbkw_elG_OGv{W}74{l>f?FP~rN1LlUF4wyij|z82e-}QTuSk8d6NHo{G5z$wXmvTdEK@C= zpTK1|pH*qom5~VHwp$gw67alU%omgTJPY`^S~t$K+8zjxqk%gJ!Y+0@ABauJ)tCrF z*wEb8$$-9G^NGN%P_kCc7VIz_n?q=5Dr?(1jL}J9)?>AJJ)irT@U-Rc@KRu=--0a6 ztk>=f-%wok64OF7cVXG#{~@^;HacoaMN}y7kjwAo-S<;M>3O?vfFWpXtV}M2!Q^vB z!*%uXg71cCu$3J}(Q#diKtFi%(CNV3B85Q6J3rq(e&cYQWyNALE6%L$&w8876>`Ay zKrl3NMn*;ogI2x6mS76pqTZ_i-Qk7M=Zzx5+2N$ltk!mGgX?*s{mV<_Ju6=vpK1+z zX$cL6bCpWIoT4h~@dmwi1*@6a;nkLh>1*RR=k&1gDC}zWc#&t=mBL* z2lE$^vF&JU+d3H6U`23bu~eeR4YE>Go2b_F$!lYwR*#y@bSL=c>pauzo>W$0S6%sM zz?|xPmjC;5`mbB<#*MBs5TnMQ`A1WFX}oTybtWT71Uz{vWmCCAJ@y;z`C{Svt&aJ1 zb*OX}rlvRuP~ihXXHiS&=Zn;9%PjeclekFMJ+3#rp%qEq&tNh}OeDQ$;yVi!OS`Q` zxg8!?AMbYtles-8(sg)#_p{U}LCxSuAyf^~%7pP|AAz(5^oJAa&^7stl-mJ)et9xi zv&i>2ky;LcsNGz!f0DZUM`hxqUENzj>Yl||;dB`1Muvx^SYRvl8cs9}e49+(HnYw} zq3#zaxj$c11Jb!2L~L!}BTx%9F@OD%z0R0@Z|V6Uhf4t~8jtrhq znxi53G1u@6I#^#1x2oW<%~{BTJB#A+fcp`Ngw=q)aV>MT?jN74k&vAIwfRPz)#j*N ztu$#+3j-|FPTRHNssjnLuM>x_o6X6Np-@U#SQ!Qcd@g%?e6IH{WFh0sel?@|?-?AS zNT#zAE{%9X{$$#-)8WX(W?lmlh8JHCH34$m1zd83s=rSLQDBgt@M%0SunvXmHZqEr zc(?q|3hN3>%`7m7MvCQegfPGI3Ob=a7ytdEprW#FIt7P*yjX3DqQsSCpQ+8H!x4ni zuuW)Bvba}lc}D;O^IJvT@!5vF8GlLvMO(E*?f0L3p5Wp?4EO7Cu!c1EYLBJef-uj@^d7RiNg}Vj^_wCl) z3HaPapQ8~UK3EQS28IpS8eyvb5UN)jv>d#*(XVJh%0!;TAJ6pdQ7F;6Emdz=Iye~g z_EMMI5A%TPGV6OUrjE^} z4EvE!(zCz0!QDrhM#3Bovban=(dBu=W>4kXJH@K8+Fy@BoI5(__HgQ^jVPXn6W#k@ zQuT8wp3oo}6LM-!GalOhSRh^;8+D8+B+Y4$N|@{z0h(5KxlKB7#v0U;qsXF{7&LJ< z1$PvDQk80Yz2(W#PcKT+t_ZWk{j$UThbZQRnOnXOE|{*uzg}iZnSa4%w#GQ9<(h5I zoV6z4jUHG2<>r=_oUrOu+UTe^bohk*AQ?f2Oj;Dl0IEh&D-a4|!(;1+CHl4?Xu6+P zhaN{D;I;F2VZHO`hR0slx?m|}LLT|GGQX30OfjigUXK&D z?A#QpfzpzS71J!CfZuh((1*(>^A;A98SLKon(9sEsM*ip!KY=P)Ho z$eF_E!e(-hlQavOl7MW|VBX4Qv#K6Y2zbS2)()xx_YKZwpEz?{vrQR_!oFNXMDu-?g?)%DRjk}(OjWSla|IsFRHR)J8UEbs)q3}iR+ zPW0t;XH(f-AzrmnUw64*e;?8F48dD5&!5s~LT+L~(GP#soU)ucJ|9~GNI?=H zuvz+nI7ZCl2-DcdmnUpB;fbQ8spNijsY3%20e>q8{;3P`ph+4_C=jD=i@{a71sAQ9 zRwclZCFu9MI}|1K`Md|5pyw0m?B)$^-QAzLUoW{18||Uv?1sG{J0cw$_O=gJf#%Hp z>4I78K0E32*jliEq8>L}{h^6SZ42tel%iJlc);PY=zIm9$Nh;9obEB;FwcjjN{a76 zx_*1(+kI|_>4itELSbgu#VshJY3~ud*~qet zh-<@wCqnWs3USDdkJG(eu;)!m74_1%+!hmt=(+6I5r&Rw)*az2iXl1>;A~*^SvX_y zIH5LhIR6FiJ+EFP-EVsbOMnbUh*4ruaEy#gdd-1*R4g!g*EHl|+CZ5|foL_mK4dK5 zm&#|id5-WlS=_VKCLgWz^-om$Gz2Y2gSQhekJ5+IeLxiaVaE> zNQcMk%EM!x+U)nH;A;MP>>4%{mAcE>z_`})MPfsZB#3)r)Jzh=v516SD6BZQK=^iz zU+3f8Fz$+HA)^1V=HPc*G5zIuH~gW)P5iz`{B`o)HtvZ=?0b!xc-kN8Qd~Slukjzw zv9z)}Ijeap63()5C!z_Y|Lzdt^o_!3V-Cn^yy)`ULZ}3M#EnyW>3Qiux$i!^TD0*_ zt0XthP*I_pz#uHVvpgJh2?cDWWqsv)!nFGz5x{RwWNujSgkD$TcmgpLfK16QH{cQf z@pG;D@^!K%VEQs4h64XTs3l^=FI9;>5GMYt8?uA6AF8D(7#6~ySqqaARORz@xyL!s zN~>A|3O0ew|7`bU{38<~oAp6MabTm#JC^BXD55UQSq|FvLp1$?Y#du+YL>$kfd z{cc~(AF8VJxLPdYK79n<1@B{trMTYz^0+NIzr&mj$FtbIcN4THS&fO~@mtdTBII|q z?d@&Gbve}U@JEN>vz_`uukN7RkYCXHp3P01jFt3HeTlVj+0=Hu(d>LsCZQ%flNdX- z*5$~4{j@K5O;cmLWckl54T_A#<#Jt=`t~zB#PPIdV^dQh>1kaE%klA6olp;IxFRh# zzUyUR&HhxCha^(2Dd*d=&Qwm*_P;R~9s*+jXCo7n!r4D@zb^d_&m%c))a#a2sqlX* z3VjfS6B52Y5s&WTkvXURKTtTP3Ywi3AGQ_37g^Y7x#PT}wAJeFqtt`dGFhqmH^aKZ z&?$E7?&j?(nwXQoG!Ado{p&FXA)8mQ9NE8dJcCqD#vd<_l@0`=hhlAZj}`sNfMd zc|tGy?3L1K#G4%Yux+(R!O*dK-L8eLf513jpKR=`h{Vqhr*)k7UiA!Pnq7_Rz{e5^Wc@Z;@ATTs`qmJJO#FJillkz_yfLKjFXwNQQHWiW}PqMOc88y@+<9?Eh(XwXARDU7+aphNxh;aGhyGG5rNmNU&p6aIxPcy z9HopTaiP#i1*AS;zSEPLEo>+>!x_Ii7=PfskNz$Bu9HXo$skC}`0!Gss2jq|BA>=} zE-Kz@+E~ZxeKzSK^nTn>dH#L~3Lfx<9I)PI6Q^&J#^+uI1?0i5IcRyXeuQX^w-}iW&9j+l?*66kMTeA&IKHA~)Z1?7D z;CkyB+sb=8Cx{fP7ZpQ;R>1&wOakrs8JKd&G=*KAK4vLU`*pm`ie4r_#}=yvugD6u z9D-{Cdp=W#HNc$Aef5D9zx_Wp#u=k@eS zDUKrs@nfIuAwPIz%O};{x6$UPyK=%eS!IMN$kAUP_BF5w{4I{(|0^+0=+*aje~d2K zc%l49o)%RiBEj<49a>>ftzu86IAan``m(1Eu8i%H_| za&m75N}#l4muQZutE~UusH{4%OxKXyVu(3Z_fPFyg$r+MMrPXcfQ}?3@Bs=2AL(O) zM!4pKGvwt+qB9POkO)6TYOhlbMp1BVDn)D9FlBQ2wJEd`Nq~i&Q=XxFKd*HsoGu>_ zMrqbAO1(rw_iaW$&bnPW%2UtAHZR(1QR{vwfNPRQ&r5`0cV{}XI6~(GX&W9J#GP<3Q7GvPmo|iWw@S zSr4$Zo`eatrlg>;*-ZsC{fAQ0KXFbOwoeW~2xW!c@}=|Tm*!;9NwYa#Vt0|cDZ>Ii z4D@kA7C?~F0&kx0=W=mUcNPsg;@Uv6D5O|G)?@Ru+Z89 z_D!1foeElt00m(TKf}13jAk7vtQ^*V4~CU{%nn^>lmo81C(Dc>kl9)b|23oE^S-H* zBTr1QBYtM}ngdpqoZ`Pmkjb7!kJg$_WBxuHq67xGKf!IBS(l6f3*WqO9yhQOgrg{h zNY+)f3dM4*ID`^DQnq*pAunba!6=IXR+H+%otAWoSswT=xwMGx3kIVxQDZKhw(%Jc zh(_@Vq(7%LMg%X~IdI3Z@fS4H-D{z)bK=s}-@7}M@KR8G53eWTu?^Hke*u|=ZyZ`s zvT*_PrASX@?<GZbuT(Un3X< zD0KSV?YzJe@lyp=acP{m?vzw0*HMcAk^k%D+)bV2$OvIR1T&4%sepzklS*0$Z2A`~EeY|5-0%)VOBn~Oc@bCBtXsT=p({l!+6|q|~ zF9Y$A)$IS~qW>Sve>2WtG!#a`u;LPGuUyfT@1O}&Bzx@p*C6-50eAflAp8bWSf6cl z_ZE1sZ+_iThEN#K9^NygL;6bMZvWk<<-MO$)R(Z=G(m6()WeWt@e?7gi5!FXzh)>U zk>G(U^X$aaZ)o+`3u*M%hliYs$3#fe{(~{|6+u04P5E!iX;G+*#=09q%23hTftepC zLJQL=oZS(Dm;I>*^1+GH5l=nkY;h7|PP&EixP}&X7&|VO`rkI`UwE!Ydp|7IOv#Ce z_}_}r6m4-Gf1*VF_RULLGE9P}Ee|40aG=&eokq3QHoumpr!ag?8A6gzZiEdOsK@x; zN)%iP*lB&~94aymi~rUj3#TYBTRy2Q55SdyVtwue0Y&LO=mx*sJ%daX7?l{`%yClG zcfSmn=LClP%6F^(YYCYwx)Rpy#$W6>y>&f0N zLs$rj2nok=A?7Pw1%G2jeXm-iqGYA09GGLXMe24*2cc~n;+-s(dsV94*YI8kJvqZs zKlyOiMVqDsbqUMHDM4bL8kk%I;&eh=3K-ykzT(V8xLy+NqAfFtRqi zj1&Tk5)}uVGNm?=@^njzO>EU^KRBX86*K^mi|%Fe)lyKckc3DGz>q&eYme?7)MP}*Y!P0uy&j;*1QhEmW1J@&_2!6Y_S@GM+1eRP0_*g+RpCGQLWhg z&nQI@x&oQ|J4IVVI>FX4V&kLPZ)$OZ_LOiq!Gah>EL6~Fdl{2gU?)X+6?W|bb*w_% zkbfCG5KAA^Vkfk#a+}ZOg_YIG5R5E1vREz^$iI&P5Q|Q&Ko!;zNY2CrDsPx*8iWdC zM5cxICtG-gtplJ$K~i_3;uMQ=BZHjIH9B8*h=Pyy1C$U*w6%V2+00{yzr5N%v|U&ujI6rpz&J+%_b^ zOu)+Z7D48*+U|&lGkifpTEsZ_f(C|7_3l33B=WhR<8j;f`&kM{_t;oToLu);OKh$61;A0wB2-hhmF|FR(5UHJ4I@eiOn zqm0Hv7KX^vB9!>s-W~>^ar>2eMB{pCb`C{wSC{!-fNkF4Lq6VGT;wIlUA1-+M4XAX z)zXqsR)z($URw))xDrw-5(xnkr^)(0TuK>)q&|)n8|zabHIc#kdjfM1JLiPv0nkjk zlwkP1{DZ-UMn|F{D?P1-ce^`Kpxf}M_FLUi1iY@MfC$24lO@7M61KWjvo&Z5V7qyI zZh*~pJ33#v<56w(mo$;otlPN#DcTa%^N9*FXlr?VM6XfZXSpck&X||3W9%pc()5j+ zv&nY76<4ysx4yf7GWPlGIim1SEkAcD4POO_@ZR?U%U-*g%;J$gJ%L?ARwRf3q-0nq zM;)E;`!jCgqM*^sZDNoy2~B?RJ}oLUuj?g$$eC~iTv-zFeHPdY(2imq|6#PC+#x9R z`Oy63w(IOr`VSQ_eUVPjW*B(K@Hj#~r~T2o3XPiS%oY$K@6&%LgTmJUEeakAP`h>0da^1)e#KUwELZX z@;c9`PCD>SUeh%HbZ*YMH4F1S6cp@vw)GK4&2iO6M<9rF@F6FAN!8R2GQ~(Ix z2rkWl<;@`Y7{dOJAU8Lntqq}|r6HdYS+UUWN^tH#aGU)Jx=4tYM^}z~mAZkZ(q95g zDy+?;`PqYohwDu4E~Ry zEH*N=kN<`9NO504Lj(kwa3M@w+?b7BFq5NaEy9==2?(3;@!gdO&p}pCM;*zqVvm~R z_s)cpVbFTlhh9*-?=c_H_YREo$^JUs1qF0~$D)Q>$%rlepS78@v+@7pe5i8aox~SA z76MJbrMvvRG6;tSNSD*zA;3cKjqQR7fzN=n03p4ky{@?gmb91hHvksF1o}LjlqKZu z^6KYK%l~>?o;@AFOu1v#YfdPT=;$r5c+Sj#eyo9T%tJXszqci&7}ZB@A=nV|+ujsl z&X-K(2-;1JqVCbrpVBOzjqPGly8g=tPTNu@RIxCG$+0nX&!MCKyPs2abvOTYQv5tS z)Ibsk8Skk|qehT+%sE*l7bO=DKQSq%M4aqxSW;?eAQp)AzjJb8m(~lVkf(UFC8^FH z^Ljd9gvon(DU7Um;ev+*%^%O(0J#p0*rP$^UVOO!R3g8UIAIPZZi4m1>=U>`bNd8T*X#I zF>ZQN9`c;DPjOwJcLPcTB$(cSOAwkK= z_>m5Exy{{RWSI&E`b|tFA`%5g!ku=4@dg&Fm-%rJ}Fr^tQJp4mVa7oM6@w3 z2{WY<2`dIz_D$tTbPi9E9RvY&u@d?HeJ3#D7UZaaF9SG}adEMHH=jLEV8M%qz4`F& zqF@$dpC;2Cb#=jX4*&eYnb5CbrKBnvGsAngY?DXKL-aoJ;F1~8JI+c-D40LFzDGlM zsu+m6NWsPsIRbD$1BGx_kR(LOgSQDu{7^j}myjqJaS*u$zdD60F7WAgLP!4wmX>r} zNW{99^gwziM97{z&-OGWHpXFeDMDG$?F8;!wSHTZ*_{FoVxNpW)*J~bWIGU;p~yb= zF&}AjO>l(r8&_Ns9hMRXJuK4?4ZdOdo`;~ivNCI%{kWQ#o?SyU zDAow|m;oh`Jq=4VZLojrLPeSxgb^%4nwe}!4AYpyx=8mo8a0V1~*WEYd? zq3$S=1EGQpDgzPyje0nieTO<%^->lB9hD|Bg5|#`($82!jV_!St$uT;aw4~*7=S7m zDI+P_Uw}0Ql6t-$I=-vty^A#l1HX+f??*}7v#vGA7ap*7YKm8uDadJ>Ij#=eSSwM!tm^gOt-6cw@N>@Cs zq>3kz502!iqVlWv`$&6xal?#+E3E*s9VZ?aWts8VK9=6!<}epq+fGlkA(q(+aW-My z1T>A;GesxBNe9Ga`8kQ7|C%fU3orAFwIWmCt8>QT_qP1tZv>pAk|CE@agX~2)KxMp^juk) zwLkI`iRgp*`*DD`j4h92{0=h@)F4%2x>G*8OfTbWB9Zxl4^>{z3%*2)D**5KenD71 zHZPag8in+FfN;1^rFZ7x)`D#TULL>OF$;jodiytamyGFlVnx@QWmI2WG777vrUpnU zk`)V&C-YLpDq0L!*$Rbfe+iK^fY4%5XP&3H)vava&}5>wF{h;o56S@!oeG&3R09Lk zk^u^~b#oKxTIN{Y0nxa(mU{~f`$$Ha*$w*6+SMMJwu0s7p0Qcd?9d%Xh#kLUfdUg`YU6hA2`DHRpe)3j@!r>RSz}{^?*it5QcD}8zcLvCn zK&-{L4Sq`qUUijYsd*L&c)vaUzOy1*$ z*WIBV0LHI2yMS>yn##uIbLV)w8BeYGnG86NfVWfcs&qUQa`-pu3hYl99I>|2MfMS5 zaU%dxUboBJ@8Iu3W$55copB(JF`5T>JJ6;PW>lW?mzU7z9^*TBow}W?UP}Uw-|!71 z!IJjtt&sX=Cv$~I@JBTm*vT&b&x;*8Tvz-6#U0a$>z}{hVS1FNQKJR9mbTN@8Stle5OA)qHRde*wuhFcICb|59H zm8be++__+On$*(gCS$|kZVEC0RF$-%aRkD=9uqQs@zPn#S~dbPXt>_1ysJ40 zH#fHd>5{3rIr`p{MMm_Tj*QD(D$YZne0Wxe~K}Kgj0sj4LRu; zXo~BIJOW{Jvy&D7A|!r4g~+`3iOfnL=7_)Nsu;Izp2HWX!{kk!SFC7gfcr-WzEjZe z9K31V)EW&%|0}fHNgzc=>$eoc`UHo?6bZ@ujcxeVcU7j+fiGN$xd|c%gEuFs$#6eCHfF-!$6p$Ajc6bW+p-SaA=A>C$A}X@l)$KxCJyKh!fT@ zWh*r?qrmWngi@NU_ZOM|1w>AN#$6%iYf6a|)>Q>uC@HhYP`_B>326TWXrFz|DohkK zqHSr1wM_=#EP+PGlQsAq)d4Z6JtUi(onH9ECa-q>Qu@1s2uYJj>GarNb^0Ic38mvC ztz(9bI(FVoQ0M)enXF>J6QBZMpR?_aoqa*2Cn=4xMIj3#!$-A<;v{Oq{S+F~5zS+# zx%29N<1l>-mZ17Y$A~C<2nmJ|@L#%&S|1SCyUrhwqh$fdM2gg)zt@xqZ(+U`olL2$-@B z4oOi`QX0LZqSml;aaCxLE@iUo!rq*uwX7W=f2s?6o0<}LiDYMG5x_uz+?>_)ASKc6 zEZ3R9V==(Yw&Bm{{R(6QRoC*QSk&&^i?!mF6%|F3D|uc6zc+F<#NC_1H9$*sxj(K0 zGGnE(&Q6tE%53{xtwUV({_Qy3ED=nqUo{XVth1-(VX=oF1F-I~O$}%yo=uQ1#qfvB z8?8oXSu60G(!wmSaZiy;Zpcgnfz&2)dI@zg30FUc=06lLza?4>D$n-&Ct**?{pR^y`Sr%%MwKKyZcP z-I8O2T7v!Tv7Dkb5|*!kMG?bW9W9oYk#|n8J&N`FI{SJt_yRl)4*NY{Enin(uK}M= zk$V>DbkcwlDGa!x`*OnkOB_y)0Mi1KLAUcj*mpa-3x=#`yC`gCLjKm6DT{3^Cf$kH zPB*2seW%1j`P?qkPva0_E0M?n+ouj+Twc{?W_lrdMRE-bFZM)Z^dBdG){ze@ zy-k$OLp5_)cng>ZcXzj&d#>;#^l2;>LvJk-W8)_l%xm-P8L%v^_;kOXo>3*rl+II7Qd%~x z{!>eV(~^-Ho66<`jMZVplL{T|c|)ir!_4Z(zIg*A&S4GrLh~6S0D+Ho9wah7P7k7k zzpp;mcn`?I8FmD*LYUZ(Zk5eh0Kbia#junc}OwO)RJ7J%2 z?-4fLotVu5O^cC~JGuHN>5g8fnfB#ya8kWnE0diX$8Y$fYI*mp{GBk zb!}ZKDkvna>$bf=EcpNg1yJ8nYq5^_SQPCc>RYZ$RRxWUiwnX_SMH#ev~NFv-?Fx` zxw+XB@CBSBX=yk?=166gHE}>uO30MO^WYsK3!^k)N=cz3_A3avvj|y8El zndk37*lJc*ZSB`!esw1%Q0_GHXhpcaJe*k4ZuUhQBIZRkZX`S6p=$NCdt~b8@_3}j zzP?cS6hc1l_U`J?wxW4iWY+8cYJ(owANY6~j@`@TvT?~5Q;F1ydr62mED>QWXV($o zi1@Pr$l>Elz0PzHEUM%9l_Y2osGf+RscNX8un7>3k)ZXB)dig?c6@dYf3B!~B`t?= zlMT^#fHH=0*@`v>rg9uXTlt=pzI93gdbp5&i2t1}8N%SRxTrAZv_D6IJfyYPv8}o} zT_k+GZ=?49ufwXeY=|AAOYnISg#f6%6X1FQKf2ksWaGDvViqxv{=Q_;cBgO`U+0IO zM2lx5BEfEvJm#`Hmwmq5E@n~vJ*SI7!fbYpZBNCKRj-2kGP}W2FN~a;OCVNyB)XrTbRgw+IN**J5>nUHBE zbIZR=`s<@))G3BPY%2{N7<8|K6w|wObLOUHF+n>f7P;M?OZ%hVSBYEe-q%xeoS6r_ zXXg?&vAJDV^+)DBaMl@;YMcmBC{VQP`nH$@=DT6I0C^LLwZdu@O}tg^an;>Q7a1AJ zo5Iv}uP|OT*!X9ZhpXtuiB=|(=bxhfreaD+NXU}D&!n|kYSkfvp0SCEs)~v}VB8!X zm2Oxe2s=cQYNhItH+tp?rXZnbiQx~Pr7)?*DFF~a;ABPp=6&C~yuH=w<@0db1o$Sy zSAjB&YW*(%w@1BYh;zWQ9%3Pj5M78xl9Q|9`|!64fciws0T}Y=+s+^DjLSP;@IMQ+ zHI*b6Sn!ye+N?nEcXoH@fIJ8g1qgGW*4yCYUmK>om+Q^&V=%YuyBv4s0HbUWGvjKj z(;eVv^tjs8QBo48AmWHAz?YL7U#ir4I!NtVsWZVQ;s`xo^Dtbh7MQ!5SIl&|IKAM$ z6d+*rJqJ+S(S&}h{e?Ndc-}w>if=BT&&yt1_j0R|j@%sy@BV8OAH7Cpu$d)%hZD{<2IGx>U2hqHfx@!D;^>mUTRWglceD{!@ls-C1X|C1WalmgbUrnZxg6ve=oNTGR8A z`?dDfWp?!W4L;SN?PWN$q9r^o-PGyT%-Y0AYw~Ath0yDJ3(uj%LDO4YmiO15f!&5% zZ>pYUGxWq`<&?48p)a#>vjw>};A!6;H7RBCVY|EPezDwb)GB4xE&F3f!0mPTIbd=7 z%Nm)e%kyEF&*Lh9Kv`2W8pLek2M%M_2U0YaBFhT+F3$kuc~&+#7sI8bq+~-RmcyCH zH@_RFF>k#vPtI`B>ON;_O4iPBKBkO-qjYX^7sFlasQtGO%Gi!Y!3OY60AMM4hn_A?6X+; z4FvwzenhNR*fO9JCqRK!$9OxVq_ZoxmLOt zU-K1Ut;K|6(5t__n-1Xgd0cqK$`560^a(z(VxKXap}u{ z0Ejg}vD4YkfK+dW^jrk>lr6?6($<#OkwE*wVin)>>U}VS9;eM=XQ7mW|4T)UWE`H5 z*W5jODKk*$(Qog9kBH^E+->)C`r{0!BAh(6wmtTEnhLHnp|bu5MdfQwlFA_)s@~c4 zN=>?(T@5imEW1Fw%C9Yr_a z>|h|>Y4tlk-cU-{=pxAn78e7yPYR{zf5BgFbn@1=`CmrUW-e8&cG^E&RA^*cpI++s zXk{3jdq90jYwGDm)fv^vuU{cp-XuV?{nVC5#i#FjglUNdVTEWNuZ)TPbrelOU92+^ zJK9|rP5Dd(>LZh7pDatcZc(Lp=5sf`-sbutE6Diz*%NO1sK33WOii1VU^UuSxnf-NuW5P&!0BJfEiA z##4BrUwxLUb@@C>0`$I{n0e()v)ATd?vCx*q%dd;23)3-L*IQ6CCYZYURJt{ATeih zchs{?CmW^lxy^MvUEyJz{3`NnkX+H!YWDri@>ya+zt*1A`?Y2Np-*Axq1nWnN^5dH zLpt(wHFn2Wlf%bq?^&^A&fgqxG{DD=e7@Z7cz6%ZZST$K?k4LE!H#SJsxt(M9x~J{ zmU9&aUxI`Lq;K17KV_oku6aTlvNFi9a|reEZ%mDT^r{ur%1`P^a!^qz6wdY*+LI1at}a;m!{wdzhzJcIJ$eZiY1^z ztZa8j=c<4GY{_0Rm+NhLRu#w46yz)DOy4E3u z$zQ?9q2!alg0$4s7H5l94&7t=j&tp9XFz7CtET3<15_p)O-%wta+Q^~*V}z>Kp~>% z^>zp>x>AXp(eF#zL=}4EucbK zN+%(mWR6gn*gF19K$}NdM?nMG&9hz3@^ zI@>WZlRvPf%nO8!3!9p{YHRa|GTE((+9umb`1x1&U1oB5bNmL9DW|}gGLDba8gMqp z&dbZ5mnEz8-ej#Vs|_-K7SdcH3vH7*@Y@MMx>=X=`)YzkO6K+e^n0 zb-OKRbK0+JuP$+N{>5i?YZ=TG_4*B5F+6XGI654s*X}En3Mhk=oLl$>4B_ScnUe>F zh6(4+XQ0B$FsPK;c(2UI;usm3-Mb7OM9LKlqQb3W%+DI;<5`IT5H<||JCzT3b`xsr2xx^j9}P`emHvB- zCw0A3H=gkO_b2RUA#bD!Zzff;i-QA$wSbL)RP@E4LUE9SF7jZVSKlCIl36X1XjX$r zn+`uITShEe33U0475!o$90}9ga`v4xmkHH&E{N@HDoOtQ+d?&|YH-T1?b%i)`+FGl{503y7Cp8ZVsu39}6o!*n z%xrna+A3atjIycGS+=1KwDM7>ny{%~O|fLe%=^yVNN$*bZ z1pNv%yjDEV#mPsG6eWAEGq|-G+|)YlYPLr{HjEV9J5s5T+2a;|LasQy^9KFebVO2v zixxXGM4wu*e5F{U3RgY`Py9u-B8}4Xme_2r;Zpat8QULGv=jGO%|YEwk4H+&f zYj5&!>A>V}9MQ@@M$AR(zq6g`+tNZZ9nMU)u9j91GcXIsBqpZM7)Cx{hYB2XUAbh- zya#;t4H2)eualEsMSDzpCt8_!{T67K^hcD428Ar- z=)?swIJ0aVuyBxtQK5=MQC2_CJi#E-cnC6eNPB7}5g^zX*wmbs4u? zAWNZ0lFXhbk^(p9dy_0kPaok%6DorkN);nk8Hs00viOBlB$)*BGIXu?DA6g@tWzne12~ zZMEDjGdCn%N30rOq%x=V)b-1jT6fIh*BzZa>e1vpr(ZEE9S2B4wfzb=43Uf9F=9VB z^y?N3Dcp}RNOm#}M=e{%Y^Uo|`F|fi5*ZdMTrw**A7}oygQdt}PhkVmDJLx$W|};f z=`}}Hv@nI4^`p>g@W>y>qsP??Y#X8eaI^8HbcN2?(5{?fXvA764ByaCPSU6ThK0*! zuRi^P=HQF~AKjpk7)=PztiU73{9k5x`fC#Qztd#K{cC;aT;OsDTnG^<`1=4!AL6Xf z?>jZsXnFZQ9d)oFV#qczqn>{K&|SF?ax3@X(xeEa9Qd_z*dCuqd3WK5VH~#ZZ>z7ce*`Y%DBsvbH}1^uqn9 z7T90Xo_&KZE;g#F@b=@}tlE}Vxep59cfjQ+_I`bF010}<12J9U78P29awAYC9|s_d z*i<^xGSNwU41fb}>@mcgP> z#Y;*>jI^bSW29q{uEZp~i-B_)Q-l`meZj7`4p31C*+dK>^Zyvw-7Em$P zJNPVrV!cG>47Z!vX`pNE!yc|u>h^rGL7+^L zuBut(`7aq91h2EQ{ccopR0vlE(l!-wqoC!_@=X6~beMYS#>j)tq&RyU)q_IE2U8fb z$tn7*NF4W{u4DQ4{1VTjWq@*$NpF>)f*EGe;bMCd+X-YCcRJs-#Qpkl7f-LYI{CS{ zHZH$vYo%R0(xvI0+wDyDcKL_}>a1F)}T!O!>?YYe>LNdGgCxCg6Z5+G`=Q2>gI{(AXqMC=nK`xf8q_ zH0L$*{)%MdbR!MNf=susy0Xj}iSjmJGpmpTI1bT8$iPA|k21{K4Yag5mzKFQ(zHGc zLC=+uVE@=bPhS~B-}|nHKzg+I&NVOPu_3!H$}^ba%Bk(uz%aJ;{`d@h~#tNFma z4-Kss?6IM!fnh%6rI7PVUvC7!Edk=2f(n+v z;T3RB5U^WKFIQ`$5DWFV9Z&88N$G-skC(Y3@w7qiwJ7VOz;66GQZ#&Q*2tQqk(PE8 z;FX5X=FQj7-D89>4nf(YDX3!z5Ac6+69>r~pfW>^X^q@rBa6^eLgjl@_aqVDKwAp< zO@VZXC}a?{u~;GyR6P_R;YyR!0kojlp>N`GC*|N!FO(`@BCwBnhqW-Rt_NMQ0?UIS zZDMSkEiE}&TjDvuvPUdVlOIz_nj*i%fVqEJQlsqvZ*9TeaElUZ!DDhSC+;|6$dyYB zHB-r9mpkCcAm_WFW-MMNBQasz7`4n0le-~{#m*tx7u6?NpDm^exQ6{^+1=cun4)XV zbd`Y0b;23*JpHP=auC#(EL#4C6);R=t=V1MG^4>5*j&=JttOP&Td*MctHaQxj1~ zpl~Fd_Z!CtHkusZ0-y-ImzEwk>hXHHj<_A%iotd&t@v?QsGP@_lAEierq|&7Ia}}l zm(u^SNF2t_PRFp?^JXn0Pt)W0!4dIrZ!qL=JYl!Zar1n!!ub1jtG2P<)6x%h^$I1Z z$=>gO!>3I6XlbLS_IM3a3c5q%oyjc0^lFurk|w_3oP0Y7lGY|^!w9?8S zK0nS)%ezH65^=9yHcHgaEXc8*NHiLnc#ST=c^jyCd`BY~yQ=Z+p%@b0!d-(BhTAKz zD^#cIZH>Umwd9$?8~z9kCt;8u8PF={qelYImIAHd{G2+nP9ww3sIlN6u33~?n^8DX z5J)Pez`mNDoGvMQo@J_dL~^bkg$w&ZNZ?MD|3$2X3m6uQkfAT)+dw7HrF4geC|kMZ zZ+8)Yt~2(Qi2D>yXRWZpM1Jdj3l+Q($vEAsaDrbu4z)tKpRV2-A}i0>zTUr@nr!O? zYiDQY{&nMtTejN4Co{Be(f;c0zWq`2cEDD>Xx_xfNDksml zkQK)r9$xa==Iqz3`+Qc=R_+^H$@-VUElZS`q~;gP@?cxtyM$H3>rbe#*bei9B5T0Y z)b6~y-E03@8nw7xS3^|)H;>2l0T`%d=iobU_ib>M?uKyRVq#kTz1?jBTsOVnk=t64 z|8_Lj8+DF)74W#i_oG;1G3a=_JZKVR0e+$cJWeVuE}|QA7L8E+NQj@wsO7`I43FTw zyHE#h<`C@`F)uW-_za5_)$K6Uj^|w`^h&%3_KGgKoYbos@zNyA*yu5#Jqc~ykZ9E{ zkjUhPh5GkRT@^QX-k_mJ6l(vG%=(o1E>D&<6qPCv<%g(8}GW_$5s(2mjlIH zO3H?stx{C=9+EJ7NE~ozI}lPQd{x1m7~s37zz9I6Kuo1gsS-yi$x3XAge)sNKDU)KaE@y8 zj}o$|Fw_u5DJ)W=?}b*jk?;N)7OtR;#jYiEqsDo!CV2yPUA(NJt*I-$|A_7_ODp(1 zON%KOlHa0Ny?lE5MLs5)JEj)#zO*!8F0p$dc+w6nv9Cuw>rAR!L5g0lQBMW?eu3{O zd54nT@1BB3Ej1Q7S6-gZmZY`WRz%!Pr*>T@B||8+&$47W=JrFiy9Kg|1?G2g2#&qo z5!0dAZ4tg-Fk=ZLtz>WU7aOR&vRRMUX)JvuQU9;zy*p+W>yr$VNSG%YT-yH-*xe_8 z)Bn@tcNf?x7Sa67o^HxL2xqI^2GeP7LJ%-Imz0-F6Y4Kqy19d9yC;IL;qqdJQo#L_ zyHz|??AAQ0s%eOiU67fTrf#5?o|c!Bqixc-A!rmJYT77OO}#_>b8=#)y@}=Y%Nox3 zo7j+30&`*(qZ4oL&08?p0>2><^SrJ?jacts>dYN0*2gqJhUzJfZl1SbqGO^Ak7a^p zrH^1=SH#7VqhX+f^EddFk(GA)#{yo{)z9sBbmXZ6hpzloRg*h>YZszkMuxH20qj*Y zQ~BQ@!oA^>bq00{uR&%?5!{g&w+2CYUaz~m2g_>XfLfH~^eUYmuX7+mp|=Tb(BonC z8Vh&Z_qVXFZ^7zUhyVL4Fc3;fSy(6YyL(=IXu1H-;lN`z4-N>7rrNJMA>u7{nU6EQ zxwaKJ?1;K+Xdt$-d{0hR1H3YwF=9UwX}{R!!nyzaLf2yjH{+(*B}cuAW6`m|rkSla zjBcpASfin*0gqrEHUwE>DQB2?nB6hLAs$XBGNmaMGYS|LZ#_@Ex4rdR>uO3hN*2t_ zk!Bz)?MkS-d4{uUo1YE(e5;3zH4O`rQ?qgK)%8`YG)oUn*wp~o8+ui6f%!07Sb&_s z5*QXLB#jF;L^yUP&Aow`2x3~#G(jcxKFKqL1I0mw?5U-EHZq1oqv6dWDre$8cBM3kL0JlBp^zGi zq)$(ATa>sN3X*UfToI5e5eR-V6O2)rHHy66#oU=?m7!G}iSmyUg6M(TR-BrN`4_M% zN`M#BPcmyHQfi+uDW6G_6|)cvv(U)Rw5W!=IAUUo(zZi#EIXSo*_B=+o<=Jc7F;R_ z2lEh%c0x(+Dg$&*<`jiY9iEO}$h)Mby&AZIDF#X{vcMz5lVlqjnNyoRNVc$`;J2;J zxRAI_3YQ1bIJD`p{dRYsf`39Tp&xH{IGhw5|Le@#;Q05xx4Nx<+uvjS<97rX(N@#` z-k4+#wzFWLV6^~tfp#_PzTe?cbHI?}I6e0nqt`LiV1F>GElMB#761=mvg4GJO02>K zg{Pgcd(y~?pXo1&FWE8XiQE?P((6xf)^$xF^D@4vY{Bqv!EXPk+Y zgXhVTn$fudG(L_KiXqtIqUUBhF^su_cVgDT?X=Ri(W@nx-PW0%AV=v?n(Tw0zkJaH zgv=Z=379E4@(u0`eNS`TvUC15!qOk9%s1 zS!BWT{716D%Tw@Uiw7^6_Xzw1vr=01kzJlbnWCJ$vW$YFf}FDQa#;nQh3)I3N3BBL z;C6aookm$X?Hx1Qv?m$oMQn|Tt6On-U6?c2lhDn<01vi-Sl7j20@}GACN&H;EG8t9hQbbWz%%ZOJyiqBQ z_Z(aW;)Z=yaT6EEpk*b#&}*@o0JLNj0v-uTFf(9q=Kv0vR+X=5h93ec)vdmMWfUkg zI?$EhBY8~>lmosE*M}op&y-XJ#O%bFF34L*Areq8T~i^BT_OHV5Nrnz$=BD%ED93V zB=vHI%iRDH#X`GuNKrNTBDDuIk6hZU-+;gP@2zl$<0i5P3Aygb23%1`=_f88JSllK z;VY`_tt$VaB-{u&Xgrlls^E%7Ss%8L;v1TIIBkCiO*;?q`1-+DhRudzQy2AOx>59o z7x|`zShsIQ(zfdB54pOUohBQ3w=?v~LJTdQGv$mq-|7q+|1&{M1q!d)Gz?KM~^)E4dAyzXOzNcM!Vug>ZZDaA z$O`o~Gx^`+c}jLR9vWIwE_y+F{#y0lYPcv?a%p7aHV3#xgug}K>S@%53`BOjzwK%p zc_WFY{FkH6p~=!K1uY&+s9~EDT*ULev6aS<69(x0NS9%JQtndwG16Jyj&Y!F;liuP zoN<1{76*)#56h{KdG>1t2c^|&@&;|M3D*Livr;l<3HTeWUXlUc-|F(xfnNh5yY5z$ zEnR@peEfFAn(E0hwlOg3eOa)y*RIp)atT^?FePHw%}Rn~Ifp^n0TINKk0pq*(Xy(B zE382$O>>{nW1z4P$nDeKQ2fs9_M)et3VaXLOO(zAa8~AqI+2+N1HlH6fuL`+FyDv0 zfKddzI&k6VGaaJ1K~Inam0-&J0%JoHS1l&6D}sa+#AeV5#iQ8i{eD{_eQTyy1CJ^1 z(*i14BWjL^#1zwq+(vyxRkXomLiR+C*QH*=3^q?K61SSoeLu-T*~f!oTb5OpR)v*R zMIH`f^B@V2o9*_z7Tt`Fu0p_OVgz`NhhsB=X<|BKl`#w&K|>xSoOK4BcseZfBy9IB zF`&+F+eNHgqHGty?d>%0+;Z^xl#?2c3`C>M9>?QyM8MjHT%{srqvGQ&F+~>Y{h#c! z!D4mF@bGg~6Ob!T^1LZHu0?xqUi^x*3#qj5~Xr?KzC0QGV<^lHHSTGjU zn^OO#apLvlpW4mFqAug>L;ym1yU}7X+%)zly|el>E}PZo^+9)i**m74q0wq%*=HEI zKnJ`Z-!kX+`}`ggWI;N2;S%?!aXSC-lm2rp80%i8VdJQUEq@4*Ezdra$fXw>J)l}c z_@yoYk`gXJyRs`mL@MPa3!HuAx-OQNoe(e$4?NMVUUAs%OIg;AB@v@e%-aEMv}hah z!l<(56JgGxDYAhoB(`(#WF>U)G0GNA_$IdNt?r*qW!2iX2A}g01-I*dcU$XI_FB%V zU^7>uwld=qL4K7=nYykFBe! z3*73h76O+Lc>B^35M@D10h zmQ!Z6<V>3Y zJ`$-4;7hq~Hm}y*i|EurWBcPs1U#AR6HBL91C-dN=cI$hU`!FAY4&azjQh~LhXiPlz8^A$nS7@HUbi+@p(@;u6$ERYFBwsj{8spQ{;M2u$x4nO#0n3D!J|Jj>$6i@9D$|XkfRdIXpJubzQC>?B)8RU8 z@J=8)Ncq7em18mHV%#z-IYRX+Ge#lQBm64mvS|@GMBN7X#X}n$*tWzll__i~pf4@}?@bjpQUgGTq*?&-h7Bf6%mQG6x`aZvvq@j7wSdwi{%dR#Ea2luC}Ss?RP&s zvpuwfUTKXdblblAvaXcz z)E`u=A<%8Zmy!q>1wvm?CEYBQI5HXTjExU}mJwu}8#Mk`i zC2dAqNCgced>V4ltTO^nD6E3#4Jja2=AuZgPRHpY6&pECd1z8(He|3_xz9vi<4%U9 zPH@=cI^k1+(7UeALThJ!4)%}gQA7X)Y zN#aRC@hV!(ST2y(w*qnwPJ}iBXAA`;Ta;3Wj*gBui{l5IZuYua;^BT7rCqC?nzWRZ zT<3yjeeVW>e(;qEo|L+SgM$DD;THQ7ZwyqRSrSRAk>0O@u2Nsd1?;b9LrX`66y?;^ zKaOqn$`rEl$>e1*lhadal(i=omzp%v7!+}R`I4!cDv#MhjPTz`J2-G0XIyrAJilF? z!Iizd&|@*Lb$Yv7+D^34s{hLCcDWx~7LQ1{-3&N#{(P&Kj?b@gwpnk#1-n^Gl}(BB zfk3q6kL5gQ-8VBC{(P?Y zHh?W?`!G%%08b(#0@o7uzz*=ml*Qx>0a}cbk{Oo_<3@9I`$)bdL6t~X4L;1Xuh7VHw zd=dV{4o6w~i1@rKaCzD1%mBj_JjV(Tk7bYq=K9B2!xEmUrf0SclymG14v$0~Fyb_U z9|PDeq>!0kt_059UkmhP6e!-zkW7k#jrn@#um3J{aIOg;E2leO_T{;!Gw}rcE~Xz( zDODn5ahrQb*#*r}*B4ab=?I#SI!L!|?%ykA^AJV@6kx!*aQdhA-oy zO#U4bErAFNMrK_D&M-MNq{)j3VH%6SLa-wJ5!sBh|L-qaqN+4eawfsZkopYh186V= z&rRtjxh=@0VzNu+sCM9U!LuDQDkx12_|Mz7A3>I2)ngm?RSljva=ZrUV6tc8?N=ME z5p@IB6bQ{3eoR$FJT8$Zhg~2W_y~kYIxK|3d$p+2FR9{^9%wuC@55ce#Ec@cF*M+H zKnPTgGfz1)3>@iPEfP*w<%?XC$2{=l+$pj3fMOxT{%P`~FzzTiK7Ld=;t&TDoNja& zO7;%6PaV_I^lK?xr6_jL*Cq^Q`OZ<;1vR|jO_`oM4BEJl3PuiGO;s=edM`KuKcf(6 zFruE+A)j!LRK7J7yfFryCR)LDkx{G5>iT-*H;xjQ&$ptTsk>bN=WO&4W|m;X-7VnX z$o^)+&E3(zWgqd&y`s-^;g5higFe^Glip5uEFZxv;F0b8P&Mo* zJTQ)U^NtavR+Mrdi>D=h&H4eAp^`9bEi^}f&LW=$(UJ|Rpyw&Z5hnyWGqgMIYEf1c z5yUq0`5)`mDh6&$Pcnl|&4a2PjBhcG&Sm+Wj}L|!{E?j!Y&G`W{o}ac^J(k}@QVuQ zbXiz>Y6$G)pt`QWi=y zE@J~NmNK$bN}vF{$eJ?)aQ6aNFK1?FJv#|Rc9(Z_PITg%YA1Y*icyyPx$NtOJYG)+hYx`%&RWW+jkO?BP zcCsSbOZk(D+@PfzTN6U20#!+xg@@268bQ^hXPlHz=!REtIn zsLFA$EDYtPZmZGQy8)s1R!1D+D zfHbgbEkXT5mWgsn?BAkLPBJ)+>%JRsTQV8Fv5`Fnm~YF(Er!6&VB&uO<*}= ziF6z@jvUhm72xHXOq#vHU43lDU9-!)6_425mHDmKQP=muBcF1{XXU;L%Ifkq! zJT>LeisIEY4H8-_2I`ZVmkjuZQYM`YgHK6w;z!wk%)lWJWVo3twf~4>HC?B2|A)%E z$frNoAl1g1#!6XkGMl*=1AiwFN*JoVKe6O%-ll6FOF!4p|El?JpYCMf^vi=S1*khX zFbB@FS=nS6LO3@QTY&v~xP52z5^(B|1F&$NekZ{4{(1=5V~Kj-g)U)sn2tuV$6lc3 zuAKL1za!&JQh_lI3sy!aBm~S@#dMeM-@p+2LN4Ax>@lnZB6LW%qTC<-y?i!Wm+Oru z6FlKOLqhh%;wkyR2=yK4qeXRr6Ay1_(5ls%#eh#egdOPf7-7w2y89DUYp; zf)sk9YR%CWrJ4mb5*B64dsOsl#_=D^?zeT&oPRwEfw(orxc%vL*)BEb+VK=(vB2gH z+?t?&1^1rm@cwW4{*ddZ6E6e*?&V6Y3Q9`4WB67nm~8bg7no^Wszn*+*S2=@$p!_9 zRSGg%1D76Ia!i-6Q)Q^|$RL|c%3L9w8Op^d`5O?knF!nxM7aeil?2CvONRUc23Q9q z9U~qP&5(%`%4Vf{A(a9qY$qwvw#z; z>;0nd>0sRNy|^(0SQ&S_Jf0p}H@TR0JKV;L4C%S*jhg-5`_y6Ra;hCY&xhU3#}zE( zE7UH0cNlcEj8|i=`OIH)fo&BqMIaK)wfU}Ju$E9y=XC!(OcK&&w_C!Qww}%PzJj1q z&WE>lwIyBdaK#{ja0gR>>74-C^w}-r-A|?8;lb!SG>gFJ8a7$resis!C$Ko=blzwQ z<1~4`O9Il3&oKkmZ_iPHH^b~7=hq7lvh^kaYb_&C#dbFH{0s#2bURGWmuT6`Zi#JU zf%(0)l5wW6v#X}1q5C3O#-cTc4p{qux;Q^tEoh>%hkqh5&peYger{#GeSs;DB^HFojEIB!)z&^u2T0MCY7cERBMi=Af zsUxUG3e-#RN%Bxx@g5z@l!^&IOb%S~S%B3%-Fy=npULicz7g5e@Ld3pJyzPy6E)V`zSs+G~wYIXKE zG}g&T8p$rep==R!eiq7s!{`^-MK2XXJ*(jQW^;wnVAkt3lZcQBe@7ZRN`SFLL)$4K zLsbc*Kr2U*3<{!RPm#Y-qoPC=H)=wa^_(@yFoTMb4S^MLiNaO@CFv|ZaGY;U-T8(#f7a%#u<@MOR-@qw(hn`1`+hq&mCG^Ifin;>L2zp99$+Un##@F27$c ze4P|2f83FkcV|BQ5oNUy7vRgDw8 z*R=O#ZjS*hSZ)LYwQ%sKnMWx&Cxyv~q^DDo+O#Z4L8Eu)Cj*%v3AzqBm^N=p__`P< znhYsWinV(tB$=z4f7n7=v!B`eB2^ck+u<-Zt9EHhXpP8tvX{R&cR)p3OdaGMj|kCYCG9V3D#A z9CyI-1Dp*ySRNW7k(D+It*0Ezbh%J*OAMA}5)rF8%&VNAl>)1iMtUHL1K^-gkWem1 zF@T|!rx1}Qv#?TFA!7_vRzod@;LsgoMTfznQ&EV3FNo;NNqJYH`nW}Dkz|_5O4)Lx z$h)aR+0Lc+w1j11yPIN5?0MHut-!>L4SF5b3 zUOI)zgGHCObKsI)gB8L%eE!$!%aIL(%EZM_x2#?*W7G1PTemI>4slj+8<~u>ER?*? z=@Ht-jzByQaw@M?Lk*huALKuA&vR%^LRmNhQ9yo)88TypLHMCrj6>H@Gq6cs(E~6K ziD5mZKPnS<>_m*z#J=1_=LtqZs%Q?3J07o=kyjkgcN`q@(|u*H#va z0g~aM6MCS7lc>vi*t;07711o!*T^pfQp)zF0RNN*2092uGWC$dUfRIp9Dg`+|j$|O! zy+N`F4F*2GGUB}k-QyL5nDTV2o=QmUAn{l#QEVatp^P{P9(*pBR{cx?XS+ilaWV5z zxU%pRpaB2*KN{_~^pQQ~f7P7fO%HY@6S*iS(b4Yk4~$F^!rYrzEnT4et7KnaRDF?G z8Y3$IaB2jC8194+E(zZ(R2p%?HL>75{A@PE!GevUKSiQecg)jZ*gZp*O#2WTP`V?b z9ipHCIvFVU4ur4~CW4=dfvFbOBfKeyMJ~y%+A5jsoN9RYm|}>i81c-{V)_kJSx@3L z340PHwWC*|^Bkd;;aVi2Wd%LOPLb0Lm~tdy%^Asa7ePCjl>0y&j>)j=F$85dkIT>O ztD-jmis?K~_3so^(a@O{8x$J7odTd1myu}XZf+1l{TNb!!3D&Dg15xy|0dxR{b9xh zD~`u*wbk_%|161AJUlsOmP{0ID4MZo*inQ_2G@zQ5Y3L5J9uD-!4!!|=LNWt3HBxC zi(2m^z}Cc10zUtDIlzf(pOSYy4$F!n%v;pPd|eiU}0WWx)%6OscLr1Q%ssvJCt}C zt90r_IFH}*G0pp?uKuoy-b%JX^(2YfYAU*#Fr8q!Moz3dHkx4N{ zc*r4QkSPY2&3-*AB`3fi%eCA^eOrH-K0qDju_V)JDpFnTcuWU|hps((EsYcsl z1)#xO{mSq5x>xJ^(b(t>uqA-mFS+i2i)?|iFeCuSgEvnJ?0S-x(=we_psXraN_zOY zc;B7i3uF45S}*tb*=)-{H{UGpg@0pPK?(u!k2B6`LO|^z_d0+&p=ovfm^BZ{Ac~fm zy}7o_@B4j`ru<>K-?j+HryM<&O~>s42iUi$z;J)Vgt9_9F4o^|) z?h%SJ5a^d99g2+svFHCLBkX(WH0c&#sRpSK@Vdru!#1;9mh)jnOQ0}W5!>tQ;k)oX zhNyck7qpI%h00@FG0?+*PzeZV#B6To!KELI)JnOCk@M+Q^PymxCYWnD86hz3jVL9a zjmV~T3SxLTWJ;Nd55PhC-(RNxouL0mbO!b>LG8>?0L)Xi)L1ZPn3!*3VfC-634(W> zsbKZX-MRo%_v-n~pG^p-3gcIAT>;B2{vIU*r7h*z$O)SeA5_IRI;y%BBkQ)!Evb;l zF?uW$6ETs!c-rwA6Ao7eI2WqqK$q`mR4C^~yza3RQUdO==hCG->~Z&Il5%qqiQ#%O zV~pr5_^dGKiLuE=WJwyOTDWrr@B7Tb+;I=ZlHq&5?hD)QjAf|=4`h_pgJqA7 zv7)5m8#ob>ex)U*bL6Lm25ufZ+uMJ?JJ~)+ej6Hk10-pn8RZG6wE-@IJ#H^2K)(vm zs3mN7)DiH$8qn>sFQ%>@vS~gZ;slW30zPK|BVLFeUcdFPK43C*4TQ^2XScy#0omR3 z2EghDykG7DQ&iBwZr9i9&d!%_{r?Cp`h8y>{u@`O0)1IY&p@92^A05-m)m(KnNr^W z{n8k?Lbmqw2q;_^qze$vF*tFZ-$wA3g-^R>0i8dq2lzbArT}7-^zgwh$6-AT_a<;S z=NjqgRpunspF*@~shqxL9?llNr1e`Sq zMdDh>kLHuIISdMA3lox`PMfbC&{my=0g^}&V>+rWdrmr^T$qRo6%l4x+B9=i}dscL%n)iJMeLl!Nd^si&GQM` z2ahZc{^N^K3ne~Z;uA9V1n-H34q)uXl?+sCc73}Qval$lCLh|e>@irN-mm?V76t*$ zH9kHLlFnIPuJ~r)TT@e7`DgaJ-GvwkU4P{v2!a0l+w0o){CN~Og~A+g?9KiM)CH<0 zsR%H*WF08Ns)M};JP7Xovm9k06`@?d0^wO!^p#j3($hEydCI3wWrT1cws>OR1ipp*LoTf+~wVuvo1>WV)U%%=rD|OnPwwo-cRFdJ| zQV;b7F49>{NzQ!3CmhoK`*nOv6bXn5uoYsQ!w+w86{M2cAr0XR|AXsP^H&}xcPj43 z;)(wz=O5w>Xp^A%!slzt1;wKGra4dlE)e>B4tWk$zl-H6vm0>XBGXRd=5NIM-C7}D z%w=BiW*VTR{6{lZVFMU@%ze+rKYm0lbx1n3<68iz4)@7aT7XNI$6^{hemKa$dl%AR z;egNWJ(K;_f>_XpVPzE5;d%WE_=(txfi&{Kg-n~vJ^)2#;^NfoeLC6BbqEmkzd8K7 z3z%&NYVo=Makihe@`0w(nbOOsfn+c+OVg*kIY+8z)y%rucR#Ccbq>o@5pwYnW#t_w{0tY7<<#YaX6tCa+ZT#ui z0Y-W4@zK)p(VtWD zIq`(NzXU=<=(QO5U0nViS6A3wP6Es1aYf%5P-iTGs?+}-^8ew?iF>_&ub>FOc`a44 zqGE-crFeToVKW<9Et`^B!_TkP_q_@dJpy$*!0)!-_wByICF|*9=?6Q{GmuV}UIr?x za>X)jIdV^$TY6qj#65w}-)RgSI2W^Zz`PG+W9^H&!_lo(YHDkh6oKe!tup7gtYwzd0T2>l=H1D=kfkL6E)5p%zme0n?I!l!61JJ?|7vF0R<( zwVSxfJ0(6@0t}e0H$c;|v$S$9WyyM>_LSO_-4}lj9{D8NR%rd=BV~Ew+(O7V5|(#h zpAw;%qBf7`_IWru2b@G;Hc}}`P;JsHAVNeK)sJ(5v1+O6>BZ|Fyqj79hE^Ol z3mPtAtLcvLaH_q9l@%cy6?;)pQAuytfmia3qBy>2^MQ^@kBY4`nbSIFya zqF(6J?CK{PUl{`_)rJKN-BEU_MwnPm5KJw1CI)&k&7jTn0J)W@qwTpcAbD(e{{^XZ z#QsM3G03rJKEiCwi+wLAA!_<3kH3Au$1Zv(cmLvEPeE7qy)1@%9RL~vRF>-SF}r|) zqGei^4Isl0XdqQ>ZTV$*^}JM6$S~dj?V&=sVAS zfGz+F**&U05boM^mQ^P~u&`>$N3g)S|4sh(*tOv){ft3Ka=dQQsU7B%?^K;GEf&&G z5e0o9Z@806_#RPNULJvV9$XO&#_~WE0(qJ!buD;TQ&dwV5Ey|-rDbJhW#lmWL9QK6 zPD~;{vl${gtBRf{u9za6F!6pnIVg#^)lAnO#H^+yClzYzs3iIC+uq6tj=51X#@m0q zY+84(VLPrD?31^U+o$$jx*VPQmow~I6Xl*?^`GPStam)icR`Mq3jIK34-?Q3`w??_qBH*qx3VW&@$myyg%gLFYhgl9QF9 zBB2v44@9|H{qW6c40E3WU|9QC%5I&|y>;@ZDCB0l8-gY!#lkL)mQ{3RVaw`H*dOl^b~ec>tS5><2(H(*<)0C8d%HJ0HOA(F<{Q z4er@{8VC~a7#{Ktcs$#Vj;51)-U~?pYWg^xwjg~VQi0hzdKk6;%~lv-=Zqud|9E?e zVSwhrYOw?uFsYs|56b{6W5FC7z->7?I;MR*yIw2-(IO$B9Tfn~e*Hcd5b@uyTN-i) z=7w<*vxr+P(7($C2+AQxV4}p7S+8{L2%VR#e(WVUxkgol!#E-lb#``2N=kZrdk1pT z)5j21&~H0^Gc_%e8J00IanQbx1!Em`lf_AcHSr(8gbWZv+GJv2klG)51Jz1tCT84z z{l|doSx>)c`#YYP-TSh9dmOo-il%nnHugRHUq(i{_cl?Eq>t;fvi95aY*uF^H+zKY zB;s?^dp<$GKCFLBf|SpD$6DRAue4Spdo`*tzHpMMrR?RI&|*Z5?~wP=G{=CR+JdGZ z#uqsFl-Yx*Sotg-+l_*c=TqSLp%cd!BZ}`kLi&y_;%mT0%Ly$lR`n=G$<{PY4IS{? z6d*(?Rp9U97K7VJIRY~kzz;o=5Z;66^9dej7dP#|+S1;x`h&r+Ozk2KUfinC1b`!7 z9nn-2k~p6?h5g2!#uF+=!ejb>M4bgwmR-1o=?3YN?(Xhxq`Ra`I;5q$ySrPYq`SKX zq#HyU1i`bvnRDjM_ycr&-o5X=*1E5y*QDf!Qqs}k34mg3?mK<`IlNB9Y#^=`zO}iT z@S5(Gx(UP|nyI;>K|NY@Z8FDQjBlEA#(k)6;QvWctx{I$KSl`VTf?1Q7)T#B;;Zs~ zr^|BK1d;M%u=@L}a(zoA7LAD84jT)n5Iqw-Fo&YBYHDgu`^jQDg$BHqm`xX6Blq>- z1GNd`eC9DL7U`d%Y{HAgF?;VgMa3yTL)u*KiM!399PEjJ#*IfArRJ|+lX~F#12Y-$ z-w|1}1bN8IBQ_qfTv%;ECzH*UHKcqzZp2SPpM2T``g{EqNow%+*aReAex2cGc>u_ z@t?fh@2SQ`2eXtPNg< z4!m(T&WYc^_%RmE4*-B(Y16>}c<}^cZZk{4+M)7&10k#`xR`x<*uKE+{z07>1BZFU zf-al&UqoG)J~}Og6A>NQD~GCN8BF}SRl)r7f$Nl%l%|lS9|=c?hVCwZ8a%Tp_;rQu z)&Kt;D;ug%#+Kb|$6c7d@d~ALYsbn5z=Bip0q1!CE5)#Uhlz5x}yGlbcS_u!v09aavm7AG)5N?`T%5y>~8NT+}3>IsHYTtADjW zORt}&UZF!xCFi}%)V4#ZXM_A5eIg?j(KRay0|$q4AXo3bO}T1_qAP~VyTp8mB#LlF zm|?m&vt8J>F*y{N43eZTuOc*(>z;L);?FFvowSod|Z~@V%)gLOXT)LySfrr(dR`kQzXN>zh(m zv#35^CNG~Jz>kh6*)S@rAuTJ*`F$My@2?v7Jp%lhO*4No4r=LVOg6m}(+i@{aL5=( zv|uM>#^0BEOx=S3PS(&}!BDAKrp(&>gMzRO zsRgWd5bc5KtDy9>t)IgP?_ODN-pOol<8^!{yU*6Lv_puYB~96e{~o^W6cA zoag2icZoh?oi%eA__3$vw=qLfVLW*{of=W`g9Ejbb z+qPE`i&cz!X;z;t`fS2DxT(3x`Kd*ysS%x?&nee>cONLHXxxS5DSI%X ztFFU8HtSoeGBkVjXi~mKQ?My#nifiaw}24iX{15zekbJIeq!7g)Q_ARh@&e5;SMDW z#fEX#FSt;Ny`WM;=q^)cxZL*HecPCw;d{U1^@AVth?(p63lZ)dn=JKw7DYw*Va8G} z1c~uf8Hgj-@cgN?LW*R(@hmTKU5ct0pZVz20}Ayk1n!SSrIe_{1NM8c`BwvSh#B%z z%!Fb8sGsP}RP#B;UrHa1b*cCmsMKTXb;g3Msul8!<&@RSXzg)qebP{5CN(e@lJ^$X zQ_^l|f9uJRO5HIlj%h$Jt3=1}+ALXhheIu5?sMI6S^4Ozo1xE{e2Gw{(MCX}@V8`? z^JF*9m9|aYOmm)_lo-0cuWMGtb#YgE@W>_&>$<>5?>6Y6Ci+Qo#~F{1XI!r-zCty_ zP^+4rnw`D|*{_1aLAMu{0!Dg50XIB>L7NlMP0MXZnURQaF#`EL6ec_f@jVunCFYnvIPWo#JwFG7HgBZ(dP!S593A+!229+MMkmO#1 zdRH(d%=II%WJ7)EW~rHvk{$SL(d~xL@!~}fBU>XmOccc25@#aaAx;V#rQ}2jYc$6B z85fe%xI~1d)AJ`L?x@VK+$Ji?oBat3@9pAXY@kEa_ocoq=*QA{k_69e!!=XI2GgYn z6pq?7*wE}&j4j4j{5;Ei93^A*4Ym9ihJ;n3Xgc8m{Y)S`v~+6TM;}@0!z3BZDpQIJF%c82{TmaJLO!_WkgVWQ`8gtd~Q1Eab9=-&;rpEjwP$**Olok=*Od~ z=;axxeF@+w@&aTG+zVOks03R|j=^^)ycc<~$VwFfIwd08Ldp4=@#P*!PL|S9+s9uG z90=2RsOfR&per*gb;Ygu(vpb^a>$~#LKy!lC!e%u>5qGkOf-`p|4=Y zh5~iWazF4UN~2<166O{X_dQB143igW z6&3qd=Jb@wi?fu3-<9*;Vdv3c?<+mo2GT%wV0;{(w^de1V=*dMkW(7gd-EZbLq&|D z3xYtspc@j^5Fjb4AlM6%IYF4bp-jpU9%%X}5hlM-CikuF{hW#@1ukn|lF7;Z*25l)HX6S4ZB3G4%@ z6o|ZNUp+nq;n@Z9ghJR>rVa(Z4=BQ%3L9p6-3L}gRPYym&GNAWdJfAD^^&<0O9lsqKJD{38qPeL zR${skZ3*vaAHu>E4GYh9JtpDBC&IbyE=R6f97FYs z+tU4c)Dx3>Ik)x~jM2q}$n|Flue59u%T@-QYN;$CwHgg7Dw$rU!d;F)T-J9mb6?rz z!B!0bW`bJzYp^<2Sp_7}JHpqF}76rm5s45BH~7 z^jD)=Iu>A?#9OF8YkTS`K^`23Z-P=Bjd~SK&`^*1g`r6_bP^T1dQ5iW*Nx#~-u4$-k@*j-i} zdZ9d_AH(Z`({}`41gX%d()!~lzP0JlD82#Ae_jWc35^#&Zec=ta3+S)6iFdXbiDhV|Le`A8 zvE&MFFD(6iu4zCeT?sgTxTlPG{I_WG@5J*5{tb|B^7-9>5{~$E$^-|G$-g8YxsQbo3M;OkfLQ;~}_; zS8~)XKk7gKSq-%~)sm46j}vh@^3!k=C6tq{q&h*2+`UOIau}OUZs|yWIR- z`t{33jc~Drg5;H(hqxF#chVdYcC>OyqI*?nJae+bORHSK7cZ)aJGqk$>-a_sR@&1% zvyRmIyFX|Y&Hf|v?ANS1nYKnq@cv7vCl$mP;E>JduuLAeh>VNVuEOSuH7;Z=&3_3; zp2a({LVQ!A%`ahpnh#6fizXKI-1V5r<$uo;Byr{bd&}SE9*EzP?5^BgT)(!zT!p+7 zF(aEP=;Z^t9&ksrw6(3RkHOv^WpCf>hkoWpoqf-E=$zpf; z!oJ0g(^i9m`P}wsT05{RxqJkU&#zgl4JHK~mJ?%1lpr-?przH~|MH;`*#L<5`i6$l z`Fz6wq*6vvj@-liypNcy^S29^u0Zq)xsr96OJ{d2dTJC+Ex~4SGbl#-yZi1RLixCs z(SPl&f7-SK;H`5R#v<3h{M|1IMx?bSj_LK8jU+(=UmTTX&zGL!r(D%k0Qy_n+ z&qk_jMPa)`TaHvIJ~lcgMuliWWN+nHXLB%YxXe7)Q5_j~b<^1J$jG9FOx$n?43pxW z-9N~>MeC{Y8Tz%(-*W~3Zf5OQ9I1qy-u614kMg@IdM;wV!JaOWdFb*f^@BwnZKQ1HdzBn=Eb1cxUpp%}K2&6QIY z@=IE{xI<@CLcI)8Wezpsr1Y_UjL1}HrJfQkXXV7X9eiM`1H1gM&#S#StOb6@d5?{Q z0EWhD*#4BJW4zH~KMU>|NcJx1uq%uQNNossT}Ts9zzcE;^mQh^kTu$M;xzzS1{&>2 z0mrq`Y?vWegNw>_BGopPjL7sl(R3hGJ`tk?wBCSXRZO}~fQhHS#&t=FiB{+nz6K^m znQj3qIx(^01F$Z>YJMENgFYcYI-1^ajx*zRSYOx@L8%)Xcm^^#N_t#^=vyiclYTQy zh4IrpFmMDM@ACv7bveg!3QQ0pZL#x3asolt32H(%VGHD%hYV!Pe$S21qI2O{LQ&2% zHq_O<0?6(i)eJ|QoFtXxlvqlp%P}T~BJ6iLXpxgG;Q0_~k&3=ufn{a!fZLn~8&+X4 z(&XfesW1I`p+gpF>xH~O7H}34z6BoLL}#ba&NR6Nhc7{8VC`caRN2bZIZj3HlC*J( z@C4llx}Nx@p8ojOmg!X)0fk_C$&sgv@gt*F@7odhB>}S7EXc*+w-t)k4`W+Qccd?7#Z_&6k${ush>%HfdP8e%*~UnW->n?Ttxv?0f<0XHWjCX z==crrcu-`6nCALzmfoW*%sR`}%&v+i#GxTTvfKTXkoACZZ-XcHl!DNhqkp4|fQ|Mb zS@Jv^Jw_;(W>Jb+pyTA_CB4|YipOWSTPUS>>bm|omRR&uBo^0s@!Ze*fo^O>5ePQ| zr{s7n)`0aw<^Q{TG%jxXj`NM%os!MxPS+Wli=Om7Zgnup$vch4IDprfBdV2@QGli+ z!ym=nOd#UV?lkecrpC4sCKL9e5M&|It>^|fO;^C{QKKm(1f%rszQz~;SdcM!)z#OR zQ3!obM_+-%H2m)B0J7cqBz5_K;oQ@^YzKe_uwwMg%wL|aFye;w!jHg_NJCj$+Rata zb;8=yG^H?dgT4~?CE+9Bo}%#;j-KeQ(C!KX4wf7yx{R9)5v-g%?HtaD%4`X~@YOhN zf&bI)=>CXuVcV&U4|q}((kcR(UkV3X!sDp|kbii{xwXnyPNubU&PS#@Z;zStxm*Z^ z#eR=3ESQ&=FC-@?!GHd(l*d6YtR+*RL9d8c14S&6bpl;QU^bGSjA2DNiGREM_WE)> zTTmeQnPn#?K7J&VUr|v(9rKdGxBTMb!hAH|G)@I9s2g^rb^c-7&4>I&7q z@WiT$g69P^cdrus2LM+OqQQra!56jh6!-}?X@d|_Lvp+yF1%e$54@(^%g~90`-Lyy z@<2Y?M#2aJHiG~!lc_Js@Tt0VR5|=xd*8=w!&1l=XWW4%HVERGQd!A4h)ZDaDwIAs zFemQ@lJdJ=fSV9%(X$f^KC2A&NS!rracK$aWZUQ5LH8YuEnW@U#3IoO^zOG1k8}du zv{})AIDz39eCwJ)W;i%nrL@XpX1#btT%ng`7P~Z9i216S_nc+8c)=bTPm!g!qc zgIw`{H`&Y0F#i%zAFZmqac&GyBjv$Eg6^(`3Jnr zzlb>D)^j0}1-O{1DQn&Lr3d)?J_vYRfJVLk2C1%znW)3KdC2L?(Q0{Vw1^mt($N!M zfh;c@xwd@~b1yLh!}Rp@hu_hikYaOwp-Zm}sr9+CRursup- z(lx`&NF9^O@#zE=xq*xdBfblix#NO>uipE(bALBj1Ki%0zV8iS_O3=Kq7j1zzx7~X z5`MW~)*%O6R4fGrg+k~SK;~ED{>Do#Ld`9mlwZEy_IMTeRtI_`xU{S0qTl@PPB8Q! zxXQSBR(yq6VkPjg{YeNfRSOM_ap&C4GDa66UJo8r9wI-UMgC;L9ab9n+o-Elqai9H zJBF}#N)XB+9SHSZdVB{l(={B*kRu0iUoyyPPmhF*p8n-7YN&tc$50jpH3{zZ37qU0 z4ru|x7kVrm;`g{(fhcRrR8jLmsk!u_5N&c|p4ElYL+AmAIG#-lC1A+QrwsX^;2fyn zjHMs+_8+{Yd>%55PE`|J4&-9S$w`^fbW%cE=yjAzGR}iWHw$fI@T>XZg|tSYXsI#^ zOJWklPoI(M1~1go8v7t6q>HWRve;TAwbbbS<%A?SC`_rD&6J-P3*H8rV1$F_zx-kS z7Z=or5%BkEQ1tCu1<*co>Ky}`P3f5;Jwl91ck zUA|FggHeZ}wx-|RPSEb)I{dp&mPKYYE_Wv`0!SHIgnA3Q2vPqgP2QrG<5`Lk2dcKe zyB&w4zNet16yE~J#P0pkbWYF?@S)m2IXR8?g0hL*FTe?*qNS~nZOHctv3>Xpi`QWM zUcgmzd+en8On=2Hlp#~+-L~ILT<0Ti-Os1dhBxQ^8;^#K&o}LNXRiQ$*!%D8MKct+ z>tjC@%^ct%g6y#S?a?$y5dd8Pz_k904}-1x7CocAd*A;`MZvBrfN}iZcDkxe+SfJG z)dy}|1R==kfb;qp(0Cs%ehe2b?%u(@()i@F{iKtrGw6VPy`T&}4SqyYwGpI<#l3U~ z_Y3kOT1p6ZU|_Kri5CT?9fVn;l+j0Iq7=A|!4}hJO!+U#uuti!sd*y)xUyR4=>}AA zM461#ScP;e?k|hU{sFeaJ8Y^Fj&Aloos^26SRnT4crz z1%@_^)nv#mWK@N8;$qi~ep2ehNXQjwgr@9~CY_$I;I+_A@Hb1)Sw)fzEuNe}Ua1UQ zVkDmU=@H?lG}c@S8}%TRCUvaB65X6wStx&sE-+?7!#E80PDw@l?m+&li1V8nXaK#S zx+?E|+EZMcHu4Ec)ucjFQOu|)r^m6AG&IYeu~C*kRA+ZMtDVfKPaxn#TYB5*eIp%> zC+hxkd>P7aozA^J(ROLOgT^7F^j{)htd2JFFjnaCDhoJw5(FHY_O5~dX zg-=f>^Ce&2ZVMW~6}UeVr<}v%;%H+N851+;Gn>nwl9-s7oNU&zQCw_ZvX5moo%3aX z<|p~DL0l%o*q2_@K^jjrCLZe#dCaVJCmSWR{lf6;aLw5$1We?r0rSPupu{IGA;F}_ z?+?JUJEwzQjF2kmNDv%!0ttiB1k(TFz6UCF%`RWZ=hYBb>$kSr9_J3^7r~5snYh|@sew}$A{8&V`+nqi;RqZbZaf;$8V1mDn3CHvWsEg%S|-nk7k^gtJm ze`pINHyzC6r5;q?Glk?#HjqO43#m~k7C6+Nr5b`0%vB0bzKFn~KnSH&ra9EhkAv8g z%+WY(!HyOf#zlah?Gs%rV5W;N2gi=#ZcYFZpBf&f$}ysX`<15;c4n@CK^4x8g5Z+s ztl!>!D-jDg~A-ikLG}`=}K> zF9v&MZ8COf4mJ4v!wS<7RPox-p*E#$I7~51I34g_B?t8=MPDvrKq=~=Wj2qO^ZOoh zb`Znn3i<`5R~dtOf>bwC$g7*Ij^mmpBEa&)&aL03^)Cpyw!0l~MdQ@;6)avl|F~et z`uq1(BZVe@yTzQhhjUrYS5I&F_m_Y0&BeMSpSQYT`R3ipP{oDAlcSweK%g2B%4X9T zbuiIjpzZJl9e>8Edv1S$_t$<*~ z$J=z`mq+5BfL}aYF6QR9-GZMVfB$UzG`|;|yV=R>#Kpbx?n3mW`;p|Ed^RY8h#^}3 zuf;hAm$|C0Zq$KR$aVpHNsQnUygBVb#@?hiz+Xve3MA$nx4IC8X;tz!mzKDIr~x2CXEb{#FmU-Yp)ee5b(9&gXHEv9G`}?_0qOYH^tiFeEGLfJY=QLAR+?oQKwFS*e5Y11^R9lJ^>I_czAdKW+xOPuvO2< zYtFW6LW6b|&^-iMc<6a(Fy1|b&XE3IxdM7t)&dYJ(**&?{gZjhES_SgVRW*)_n3tL z_W|7LSoJ|>u!{~T5nHK8T>7d7@ny2OCD9|?qpM*LyT*|0C)-YBv5CQ;7RGg!QgyPu zpsOOFBPzGU*ir4X9Fkc`8DK^dR!LCNOl936BpF8E4X}kmZP(d~Dw=-BG-7y;`-Wcy znyF0Lk5S+Ayxuo_5d>YufI^2qJmV6E zo6YZZkr{=8L}_5C%Vj$-l%-L@y~=Cx;-3X+V&_V@V+__r$wnltoxdCQ2AGSBV+1&A zXdsu$@FGknO+$lM_vBzKE^9mNYjZ6jWqxMuxTdvMXO7 zWF{R0gPpy7$P37`x_185oET75Q={%KkShA7Z|MDfSi~v)F8abfZSu4P7a}h$Eli~S z_VwklK*awnmSFKDf+Y;j6FGR;thg|%XsGd2;JE<9v<91k8-9Pl$ckZQ>kxyKbT)f> zg4w}oagja|dPV5_s|Mc*655gX$0G}q?P&bT7~S|eFWyR=xJ&J)HORjVut{V+2o zyzl+EXHKeAag-bx6ooPNOcs-2v?T<&k~HA;$<0tCUqrU!D6B9?E}o7MmrF{`&M88z zT=1OO$$Tp-6OFg-4S+WGt%Dzgn%$& z%uJ+ppmFA^P$RhvZ)VK$3?wTuPhiR<6tRLAwZijVTwMAkFadrZ~})w-!OCvocv)oA6Z&iQ77An6^EHF*nF!Z4xS;}eqT5~ zmeqyNKTziEfyjokMvgmRgc8(g7+#I_9RXb)e;<#M)-g$U^H2#Y2u`gK0X-p7L0b0! z2T=f*!qw#mbL7uUi@*~CKDlFOTu2InDj*7mC_ZGklZ}mhgDF8cm2QY;K@IR%Lp;RP zIIuGc@3%=}|5*G}RP8lO zCqN%8ssQDg>G)ihBt9Hg*cs3s{f?%xz;8GZ(NS7T3OMVj`oQ6m(Ns8HKJL#rw{xyh z%O4+)wT@Ru;1uNM=4bivJSdrgNf>F;C6}iXk)u=|A855YKyC<-D@p@Ub9W9I@FhHU zOO>Dnga&$F?jII9&%YGD6G$txh|EwTuAqJJjeCa0A#$SgFiB++8PX^*gMB8EopJ%w zP^cf9IEU!C)=N9}kwdG4-sEDjjdmC+fr_TP#V}^*rqFbfRMrEmMW zmo7=HC{qy4@1u)!!LSaku3G)nnj8~^E3h-;8Ur~J7(^L>n}QEL5^KsXXcnGg5X-NOYM9`! zZ8Oarlg1fHr^%0>B1TmqUehnM@vE0@hlVA!SK7*tGi1T===oGj(A3Q>Nn{n1ST5=5Uen{+nw1*;`o%^d197_(F1QA1){r{r-L|F*aC;Jx)qqk2YB#px>j zw;%mk?Ml`j{dIWsU^LOC#a!TB@YKX%VWn_!hW#B{ey%&V{JP9K&Cj~bTKqQL?Y1`( z^`4f-Q7PAr_VUZ)?OSsFZfY2_zv_H_EbZ19@AedJ!Ia-k&g@H#iIydRfuMmxX4sR9 z+{l~`x3B;V_dDKXb&iFUIWA#tz0CKJ`fh@`40gk(OTl5MsvH($iB~Z(s+iL<7yr~O zr2jcBdZ!R0S0bw?mox_UC$}a)M@3!J)62uH)kvi*(y9qrBU{6Ab%xIt7X84CvL~- z-q zWTLDUWUSBn){2TXWlMN@cfJ{ACjR2hmXB#0qk6Glvg}L7G%hte#M)F<-3)Tb-)|!3&@+=0i<94b%02v6L^nd5Ngk zjV1e;2s!@QTubmSB^nmO=p0eKaDZGT$J||ZZUYdEp>1AFidCPZBVRr#5>2(_%->k! z&xtpqc4qX(6Y=|i>^d>=AlO5Q`>sngqqK{{5^Cq!Da`NxJO!*5Hc3mIk+__X(7rIW zo>kdr8%Ck|-nY0+#$E1HnJzr}B+F_+*Mw(ABP_wb>rjCbvg_4z@F$M!zU4Eq-?Qmf zRY}l<+;ETvn;8+^@Uy)L>9AhkXApJOGC?0UofYrhBri+=Yby9Jrtj=JNDD75FRkdT zYNn^BVISZf9Bb>WcuC>BvUkYz$nx_>p2_;vpc?2zF|NT9b;AAqK2WIyVeciVZ4-fq zjU&_RZ>*b|l#-K{VPdGRt!+$jr!lj$u*adEtD~tsHiv}3%&JA=-x&i}eKHh$Qdn8r zEF64;@OpU2t~e1}-(CroH#tecB^WJdE5C$1PZcWao_4nj|xp360b&f_9<5p<629UQuG z+?Twohm4bX+jgs-r8a<9RoyX?yV_Z_0C=X;g1vhq$t^?*#Rgk7d1&f3b- z#~P~rZtfHEv%!MprK(sK-2pkHN+OX|loJ>QZ$53C?G7#R@2It1t@T05TFWh^>ydWn zsG)4(ieRU#-^$!Sg|DN!xv7f$*&AEyj!a^wXN?)Xz&oEp&eylIhGhs9^`03{SK}w0 zhRz06FxtN5H)ALTe43M#q90;!U8Nj8VMyK)U&|7Paummg4i2)@w=^T}QDf}Uru_=P z9U^$+2t`j@{~m{$&SaNVR{fT$Nsn09gEubcO{~Nz$hNK}U>-OkGN&XbR~!xJg@Zsz z&+#FXZJZMFejqF+iO^4aBKZ8rx8v}4jN0{v%gfvgGw|AE+LzJ96!sGD#(ef?5?<92 zwIp15_e-BYIMzrCxrQjgN2mUj(Tkk>cDpWfVX1&8#J)m##Xw&)GaMbobB zya~;c4Z>SmTF~Bk*YF4Y9a^ie`Op8fZmcyligXBDOhe@L1`-hR*w50F{t z6AeM7k=ttp9D!t9Ehk%433;&jX~!gW@=>XmME>oBg?6M=v)?1C6{5?KJ)MVkG5X43qdh{tgA6DaR_3=)s6L ziQ?_JBH0>|F2KsK-;+gybC#FKKm5h^ya$tAF&Y3vd?rWB_?KrmvF;Pw)~C=}l!IBS z!f83bi?-7N=6^?pe^ETsuBriR`Fug5n6_(OpW(y^hIsE>KBb(jig8xM`&9im6V|5s z0GmvNdEZQg6EU-XvlJ^>wfb+P>R+n?enULTeX;!0X=3*Ca<2RPzt$|)NOMO*&M@M& zgs6O&WSB_T2m^_egy8?m``XFD-tM;z!OFU>o*vrD$=EM3j9m!1+xQ2JdF=*!w9Sgp zmX*8@>ud780m4Wys|<;gC8`BuC#f*WJ{FmOnb&H``{s|Tx)_xzOUP$1!`#EQ#YGW6 zmIVph!M&nr7jIt0X_^=&Ju+O!SK=H7t!9sD6lZU>Xhrsplu1n&rcl?Ls#$PhY{EP< zYU&{#Dg?7zCvo`<;;(EjMa@G9*M7CtH8+Z#TplE}(?6@pIjdINh*8uqH_$MTIM0G| z-y!sY5tc&4{^)3GYHDX^_jo*if-g?q|ppA*h*U*_b^r-clnDeF9)Lx=Y%iyKy32)p3cdruO?YWU3mOiC!IYn${d_ z!k$NcN1>D@_o@3NipM4<&QnfnKgl82Tsa}9;h{%GZs;X;lk9gI2CSd+wV$c~ZhL8I z)KfX@`4f-R@pqPfZBg6YWK*KyNk5S+4TqE6i6VU@Xer64T-n6VbEet-LOS;(83ikW zX+RPd8pwv%^OS`DqVq0jEbXi4MzE@*a2jHf`;&|1yO!SsR2b+6lD&MPqRoDxm7N}MB@u?g-V8%Z5)RMoZE|z?shB&W2 zl@lv%qF&9jl<`a$5V?-TvbQ`Oh&&kFou@s}4CJ}3pkD7NhC^+Y^7?wHs-!%rea3*ME!aQ?s)k3vdFm1+qL`KfZD=Z>0!!geT0*GFs72<7RBk*}@(g z>5-tzb3*g+PTAIPiq-b8@m#4fO4PYx-Vg?VeDqodv&YXcSDxE~xlQ~KtG7>lhB|Ir zHAM#A9#0In+!a!$WKFJZRv`#vhNq^z*&XI2igbt`2i)#8LU;I9TCWghVf{Ca>QqW^ zdeqFygLCcmtyN#ET3mE}joj?D78@syOu3mIJnD#*9rBdxh)KAY+9FFz=w47D3B8;5 z0ysQ7P>w$4PA3dJF~+CE^I@yV%kO%8B$Ic_AH*k2oq+Kf=l49K zkJM)otrNc}HYD>QO{LE4acX~IVR^_vI4B1xxp~X2xBc$Bo$bBai(%dN+~7gD9V^x~ zoPqZ{aXrr4AocMYeL^g;KWFIgC@-HNcprd{joebqch>~U+PiPgX5y3P-|d|d@oMdG2MqN@#)ywsnljv+g^3KS zbGZy{rdG}BaRdp%tr@-arls0P=#r?+T=1~hK|v7$7e%|}IgVLew$W?@SNHfLwTbg# zf5>SP;kGW-8ucUx)p|P-&4tVy_@E;N?$EQo$%3zOBin$GSS;4Tw#I?jcxyqAqqe5Y zO-b$6Ijg!A!^6R-&tXJAhlmxmj@cS+4n}|t#sO*vLZ6(AmWs-2GQ9P`f?BWGw(aJG zs2;!0@EHvLH3yZ893|3~ZjukWx-0{$$UkLG_lDKg^7Fl>rkT#)C_{cdK{ z7(t!wg^hj@O&qizPK0!=#t1KZ!H|Tw?94_)Ysff+9*%2uH1gi*ZQAJ#&RI)?RQDRA zb6K#TtMnE<5d9N%vA!!opZpr-0)cfbizynNQb`vLD znkyLsK{E;#!QpdP7fDAWUBl=DsJnoqt(&~(0m5rqH_G@To#((D~Zu68kdP~`A zU~Bv0**Mv1es8hN#K=P8#Pwu9o$>SE<ezU4duqIR-8-XUH9Z;^TGRxhXj?up1PqZ-bO>bh(%~w)r}h4PFe!5-d|z{ zyYLh;2K_k-YuoSk7h}Dy{rYM{@6q5p(FeJ!PVd`$9g}WT5Z_4 zHeA6#F!uam=cEJDJ^6y4MHGy^4?C7?SWP+~7|g%!Rw(Cu^*I0au^J5*FkfUpo=4xD zH9Gug!a1`E&H=r991oMMy~%W@fi&1YLtaSN7;eCTcpAcbNg(oG&nmJQj-GUE-z^_c zsi+3!eAT};ewV85GB$KsH4f=s)Rrk`nBK{fz8;Km?oMKi?XEmwf(9p7~_{KK0MB6a@nT1_!m3LKoJEYeB z7dnSEw+Bvq?iQc5^yFVHtrT4B=7eb{o0?CbY#kl7zqNdQP=Z{yYP-fDfV}X(+hJfA z)&u_zpw7BlG3hl)^>lX&v#@;NV&hHk>JD>qiEQaL5WB@KL9lT14Tn_Lf#5>zaA8!j z(XJ0xMxGF$>9|7BVvc20vMJ6*&7MTyO8xj1=|~u6RVm|f2{)~RN@^%*l90juy*nH< zRK_yO>%^mcA}8$0RW-{A)(T|V8m@>`1Hw%|JgMno-si2?yTw_vbN|O*e11I9L zT$Z01W#Z4e9L|+}A4NbB`R_Oa&LdzYwgFQNV zg~Z@;*gwt}4EX5*{_+)~ubPSRQU9J2{&ks;&J4$7^t?UiKsAvv?5|NLN&n}imvNPVGl;-Xeqcn=+44D;&0j!&!^oi7HOLXN138{q zk(XRk1}S;T@_*n+4_vix_*uH!;}4I^Vi=p>`}fcUpJ~Y_2w~azFYDW0!`f7%ef{@` ztKAZiWxI6K}tdtX?=F^#wKyW>3n z^Y-hteM5K94K5?o<`2qgWz;-n2qRPa^L4?P?1&0(Pn`HH;ZO7H1n=8EGj~^;ZB7Lr z%|wSEm6yZ0lW6O>ltZ9m7!qa|4gPw%#v=XVSMXV|2w2}Q&fdU{1;2?cEES=zVEGd$ z%it>vSgkj@DUpf0NzPAC*G7M3`1cvN??)_O?8j~FLiYKSj$!5j%q-)>g9#8XvirGE zqF?aWDH=#+-kdC02E=yV+y(VJHXHQQO(SX14f|uw=2x`CE_v;y1dhLGazba8$XhU+s`hD)yLp$38sw0kF0+b}p|P zZMDR#6Z+5=cU89f#Uy(Jj`u_aQ6b^)?b8F*SNZDsjVa5siQZCF0>YC$=V!mXAtO1E z4oP{oq0!Q&1Qc!l*hzHOhs1B)VZMT*No z;oq#`|!**!*R`aU86bttFr9?1lM7B!h4r z-nQ1(R`+JEW?tNC;0gTFk~Jte=b(2 z02Bk#7fi;#q3CZum9HyQLwSLSbL6!`gMlk^+U)b2KZ$-6571F8)>JOm+*PhH3s>Kd zuVN!yYJI>FkJxBn%^jUtQvDcX0wu$`|5qF(l8^2WD5!|RIzNB^XS-UfCg855jRABn zcf35%J_z&pK1{8eeEBcZOK}b)?bM76Qo)D6<;tr@ZGT2|j04QiQnN(=JS~Fchy7&I z4&tgKFkn;H8lnWgIrLWK3tUtsy8Ygwb+10}{N1B7gk)Mv$VE59#(tS)9Upb66(~1| zB07FhvxBh7yXEx_6D|j}d_wFmC>x+~{d|zgxQpLdEF@ff1k#V2A5FY=sjvBJEO8B8d z-;f&b?Lw1_+l{^~9#^Xlchx3Suw#9VfJR_AC)DDN4Q{aNx7Pt=+zya3Bwsajn+Cro z#%$wnR?`z{jQ`FW-jJFKd@sL)U^5D_z!Ib96Qv&Q`Cp#H_wZcv5#B`^Dw1{(?(dB6 z#@m=Y0~%b*A&7gJ2g52Q7`>pFl>DAgt#^|Z5%h<<5=1&iH(r6q3E9zH>wMmtBl_|; zMV9pE>!eA5XKdXDQ(m=^j_A| z8BFo|R$e|sNydqE0Hs`1_W2*TeZ829*w82l%}*D6xxQcBCR3Fb;0Yui>aZDKxmOkbG}Pmpse1}Y4p*ra;N<%5?fo8q2JIDg#RF6o>6 z$uA5_={b8HkqBv1x?RKBd7alm4 zj=^=<>LREbo_PeL1Nh@V!+&fs{8nsyaZUuD0)jVj&>b%!2-6{_?q4~}0MYg$;sjdSb}Ls0VN6l6+_qkQ!>CK%@cIG3o|E2Ni+cvz!jGQ(!&6*>UM7f)Ys-3dwX zuA^h2)AOp~69;6TCan<|gyETD`xKF*sFDYBLiYdxmbeGTb(}h;@CQ!Mb?S!T84-?^ z)x*X2T4HXy*q}y*@rAO*je z+0??GaNcrvmX>1cm`0R!ofP-q#EbowjAZg8;kHZl;%8=_wtxHY0=k{%1y1Mt3~&*3 z0%*#{j zvLhn{1H^iikqN0kb(U$}puk{5F$UT=TR&wBdLeRkBjUP*<&s9fMuHbd3K3$g8bv;7uY<- zHNdvF3F!qKnYKX8_HZdcl>idWA1 z>Eodc(r<=RC8`gYdyto)qyAF*iD*mk(GV#KZ54{{=Xxb%##teG3X~_oh+Os1WHY<^ z?@tJ$5FyH`9|lMH9=b4*JY-ERtUc<+bQp`HG!$Cqxbu99G&Yb~iSU;?hJUDTpB|=5 zEJ{~AGa>RA;Njd&3Lyy>bLPt2u?_0xqK5J)%rLmLquHN=>6mn7!8F}V>!Pi@q``rA zIGMcf^96)sOm!Z)7#7y$P#8yMj)?!!=J(tEeJt4FRbyWVYiny+S$GOjFt$49W@aL6 zK5n`tJhsNJiAql;yk6U^k_hy2@7kt6y@zQ&`bp~IjyDmJAkxD`a?kM7!B;N9&i{tH zY?R9r*~$43CJ3(`0>&BLvA1XKBU=!e9H{y<&j=?yTlb=qkBNJ561W1a1G?>ayiezF0W0g#x4@-HtZ2HZPD-$xDJ(*SxE`t~Ma5vrIqBO|L zK#Tu1cAg>j{8Q3N8N6pi8`tM%E>wG48=@6mJpa7r`|R`g#iH)3OC;5K zRif#snVGdXZ+vZ_b#-bXk^K52JlE^<{jDpH`-y5+b2C>`QzNyX8+5h{|8uq8h3{P7 z&;MSa+kAzc2B%e>Lw=_XI%;(~@+vWJRX@k+66EigPxF&;UM2shqq7RDs@uXaittNG zHxeSnct`oyhnaT>l!|ViHa{inUBoY{D_O^cob7}An4WaOFq?bnqSBR85Uw-{&GC17@Yt& zqo6Ebp-J=J`AF^?%gPr!v;gRy8S9ZlxZnVd|KN}UsH0?LOYuJ)%Bv|VYOLk$D6w`Z zl;W3K;Nc$y!$luk;2)K)s(zVM5nXqwEVC{zDXyZfDl3h6EVV8!EoY#q0z&F9f}x=) zKJ}S-_z!p(CiXjIa`=q8Py@#=aZYNq{^J0HF*G!^N=8Ft0d6tcd;cz$y@`hB>GN>8i;0OTQq$4Z0+Mw`fbaIbuAlDY10l~MDa-DO{jf;KLxxrp zz}iRN9xy;WlCFdF1rZRw6W|u5CNRRH$Vx~t(1!fE#5fXp-rW_!HqH{fk&SUzf9XL% z5bF*#?Lqj&0ctRCDr|d&sPS|iW%hHf{)Y|P>Qt%}o&iP8Wh!=dLYN9pQzHIzaDN5- zklirc>kV_brA-MLH$IiySx8ehstNcka(<>_qI;z9rtn7nBB>b{w_Op=kPmWT9Jdsf z_OQrd6C-rPBXr}VgCk?1FBAEysw#Hh@mhR{!t8$^#IrR9?H!dw{GdVJW<_D`!7!_g z*0XfBz*j72E`rTnRfLu=CeiyvuXG+FNH)qZ3mvZ-helROJ;=z4s2DU}4A;Z+6QYh~ zH55vPOU}SsM$FB}&doqawl;a7N@rch(o_r zPswG(gxyuym5cKaB!Uu!j{IVMw8 zj!otW#oqnpwCzdMpsMkKTAu$Ij&F#co&s`#SGATB(p3sGIP)b zDA5V~Wx8Vk>@z{Sa6cWy`jpK5ZN_0f2?w-YnYv?IrlOMCUqt7oQi3<19!>hQQ_}y# zY_>)J?S!?H^ghd>HhfMhB|Gj`&lQ5u=s@<60>&XCH!ec{grUqC=!A>kJQoCyM9-}OYfnJ0`hyrL{$VOs4s_i z40i<`oYhri+@00+&D|B$wPir2vVFEbBESB+yzL-r)O7HQDu1{$1-pL)3b+4YA)3`N zXt)wlj_~p3+b*N1=>5tdl%T@Yh$7>~iYb_wvqZ*M;UF0+ajA|tDtH)v zSce@m#M#6*Mv(_4ZKH^+f3J;BKAq~L7Y9*6?9O7HvA`~3WTk?=&RG_I#SWFP0l>5=GyGK-yD zaNVX&4jur z-P-5eP_{p&VGCe^+wo%XJ+S@Qjfosk>f&n5vYX;LYtzfi<7VCa&`NIWs`NzKJEsKF z*CKFPQz&F}MYu3feHDVak%C$yrh*B!I1V#2_Vy0B=PF)hiLtTq31r1XmBo~Gfdo5( zmYR|H>9l_RNV>UQ?M6k#ay^XvmF3X~-51%>tt_E@9;M8q&7UkAC4(P@4JG%%>j()No2B+eiF3toE(`v3U!{87pEBYz~zG#^xdU?fqO z*RQ@rJ&v1UVZKsevDdn~L?AT?3+t;9JU9auy5aB=(eiKaK zp!=*waC!?AT2Ba=96hhb02+At-PUmy>naT@Z zsQj(}bZZ6V4Au%zrRxK$NLquUye~Uv*w^b%!T15DXb(Dn=uVHYsicI2zV->%(%G3y zqM+nkUqnJ#%R?XyKaH{s{IYDWA-z>?-XuLaukL*&wY-^3gg(dg?vYg2*NNL;KN{R< z=-yD677wzP#&%frNO1aWU|Hp!hAzO2CAr3JxyH z%tqp%oFx7yngxVuTw?#q?isj}oCK_STaZ-C+;keZ||l9HWEv2;?mC#^j+ z;bPD(w*s*&A|B026H}elDV^+BPDyobkTEe?L4w-@8PvzDXs$0`naHSEN$GegNO`$= zPr2WHu^1zHeWOxnuWI8>s=pqEu^Bq8*@Ns20&vGM8o9t4m8ix~<&7OMii$U-|K+R2 z@4N92W>63H*)uDEz4eBg2+}Wg?P85Ti$P_6{}UeJ<7ZlvG>!j4%j7khY_KNDQ>(6E zKMtN}0yyW{`ssLR?m%oJ9#>t^(_#31JstT(wA^+Wbj-G1 z>o)UKEb8la0ESnZ^?Z*b##*(?Od*DI+S1D@*!0AzsV2 zD=@o6xH*WttCLsXH$u?3j*gfX<5M6UoNayG8} zoS5qf@Nd+~>BxTPRn)bVQ?rv&n0?b1aP}6L4^Wqmz0ORa3o(uopV!pUbF9>Rn9cvb z@TlBX&p5ZZ#9;9Lh%%lDMbh$0@l##`m*M2beff!>-&R=Ia7*m;_3Rg4dbICe zV!q^EvVw+CRX@hl**v9<0D%@T>xE$75wRGjqsa=vL?E2G9S}UvD@(tNZ=^5H(Y3Ik z`J}3>{HCv1J}+RJjEt=E#|X8ndRGW_&0y&3yX!Hy1?ExRW}ZenqY9%(HYjOU8;61n z81P_A(y}oCw*~A{^acvcT?SCXKv)d)0kWF!@3*7C_8yo=us}HR>J%`lfo68-)0M5N z<8~Z2vXFO$#J)OPrNd|1{`%q(jC{snzcZ;Q%iG|1IQ{f@&Fy}YMXP6kjKawD)9{~a zJ)u{%+TT4c;|7uQ7XR)JLZclPi&>Swu2p?{Q3XYC8$G)}Ab&$L-9yf-{O-{AcP+|2 z0j|4 zoSvS3ba*%!Sh>8@vHv^%^Zd=b@?tvdN>jd_D8)y5xzBw_hjvz7J z{n@396s<=c(4|hio2ugjK>}Uo`ld)qq&GCYq!gTF4yO9*g8J&I4I^}*ZPB@Vc24>s z2!_8k7n9wg5{d>J4L7?Yu;0iu4x7W{-IB2O$G><`r}JCttnXCyPj{4~xwyE5ZP305 zvh0O?c!NhXJTk(H#ZgxHRxPd3Mk%?J2<>v6FdNQkbwsov;I8R%4G6(K9>Cbc<_Gnj zuV25u1mRo%>kfg8C&*Txpro)_EtgfbbD2%&YuNmD0*Wv4SGVh#EQtPAz(JD5y6biY zxQ_r7wd=)JASdVdr#n8ogGmy_HkXMP7w>Ctx9@8^;w+WFoo4`2_!pRA5E$Czo{jp! z0tt}V!TtaJqt1sBtqG3cKzCjJsR~Gqi0sT(_CeOtoaFs#D`s=R5;uY}4xRBp9AMBYY z!}h_6GzC?;q{Fb9z;XAmMc*lDq$=BfwOarxqrZW#2Tt~0sad5QIw`XaGv0L7^Toj3 zn_{@BJdLRHn1kHxYS+mP691rn3uSec7{)KC=nO5ox}x^(tG-=O8L0#iy=&8Bqx}!k z5`SI(JQUuwUC~e8lqy>QPr#)F;Nu^`%bXm2K3x45ZeUEQ=gR{`WGRPRF^4ocv1WC0 zPlff$7^9GE1WryZF+(*yGbZhz7(X=`Co4K7H8v^XLF)}(glp2!jt9zUU~fmwVB=UP zv~5B-8=<@DLOSOa58lToFEZR+o;QK4oArBdE<-M;(;4+*A5f|Nst(j5c< zi}q3S0yUv)akfOU%Mp-z3ZwW&Q(4QJ)I5{CR;*UQ<_4d&y)?X|;0wX2i~gVZCS$2F ziHV=u@Oa&KK=iV0o0SbQjm>F3x+&gF(PZ6aJrYoPldbVAZBW1O==yjQ*{N~pv#aZB ze9=XvEiZ4V=6a{EnNZfBw#({>q>+*28-KqWMl&yy`o(B>SQ(k;JR7sVkkAgT(`lD| zjW9RI%0_WYiig*J$Lv$45%i=Spt>RxaXId6g=3ep={?7rWPW{_^pBdKV`EUW4Nb}y zBl5ZqrMPu={-v?Cx_SWf;)g)L2u?;XSpq4UVF}5xKlOGa|C~u*B_?s*mqF4f;P62@ zAdRmYjy5LD(AvP4XyStHYd-6EePfJ}7r%%I@HK#JuPQPRJAMyHE-g3q#M>kT~_U|#~!D*>n6UM_}XUYRQ^;y zDR1{aT-ce{Xk>N;7En4-_rBdW1)=xelEK8sj@|i+o>YyhHqX;($wVmv7QLs6#*T+! z{NOCETsUPwMp?dD%rszV8{DX-$LcowPvc}NFHfHKlYKpbs&=#WW_O>yDEj@hWL2_N zps3!441!6*Vw}v)Khqv;WAO-}&ZDF+85|tdXz{#>$(#7d<>@##JO@Yv-TVJuU{_e0 z7l!=!g)?NrlN<?)z4>6u{y~G) za!*cN)O2IXOgzf%3~Ta)HPqH!5?83Lo~*E_(C~sWvA3^}?DPAO*y$NX12nt=?-VjR z241>HU3q)hkQux8#e%VX|0d$BqJZkYx6QEpEBLGYmbJioLGirbTuajNCc{>Z+b}4G z5?Tu{%OOLcC)<9U661Rq@Oj<`(bO;@5x14b^@oRjo%cO3{*fz09d}bdAi*wY%dNYQ z8h_iL{^$S)8Vkf89rh#FT8RJ=rMmsCdDQ{HeB9svfff%<0knw6hy4zF8Gb@agwK5m zvtjx54Nj+4Z+`g7pFb`5KpJg2ARlORuK-K*DG4OEN&kIr+KCm> zp5pL%FAq>n4=+zd`a~J!4Rg|OKS8g97jV7dP=T=!HX_O}YEcXc3+!JYtU5^lONB>t zJdCXe@C9G*rP+jM>v-=rI4xMJ{oV@$8_dtZdTHkgt-C*tYd=S@8NkPQ;iHI3tA7oJ z-ax2gXuH4s%j=QFjJb>qq4kiueJwtW|Pbo0)_gh8S%Rp0Xl+j#*I!6>9;bYD- zGutj%;dHBd^=_^W#CjOlrBqe#^6cFek*VKMPcB(Vobnb7c(dme6L-vdNQ@FLj)B-- z-$8P(g1x9ztzv8sdswcH(iF}Hsr!Hev71dv(Jr6mVGY6B+a@03-nGP5Za=pDTozqF z5#Q}&VI?#f@5(Kaf`kxd*FNq?7Vb^vlddamee*Uq*qk%!|t zX)DpX?U++8Nr=D>CQTw(XBvIpT#d3j1B*=9X5W`eflL548s6q{l*9ErntliAhh*LBKYP}NgF}=_{t8TjQPtp%NZM2X-#G8xvvgXe z$5trhh~Jkn1=9H+Jo&qWi$`E4bDDvPjHeUR7a=Zi0-V4!q|xjKqGM;2sAbcK&L@i( z)usz1gEa;dvYE`%*{PWsEa$mhRli^M^5NoKcJmCt*|GX^pcckAVkl&3fCZ*rF&NMP>PiHSchL>>GGfNx&drozEcE z0=KT$n~oQqaUP1}5-6*+wdw2%G{WQZEm&|XX}aGEzj)f{@&{^LIWb>0k!+Q zzF=#+zDeTd;_6hmCwMB)qfnSAPe(L;;QL@Yv$=>Zmt1b^xbh=$W_t#Whd1y#^U^~$ z5cjZo={t;D^AvK4!TWE>I82T3M7*P8tg+~Kzw$(UHdZl5&SP&5OF2+Wi@% zRs`?gJB)S;)ckqkdvU!khT~icgfQSZj?gv2!Bw0c%_V*0bzel|4(i(MMqpS4M-_h6 ztLb@m{)me9MVZbEUK*n$_Bdv%3^qsLAlFv$CX428%f;##tNT9t|NN8H7n_X7Fqv;V z-S)L~@lTenI95)6q6CrFuKAthT9WL8w9IBhYV3X{_lHR^QT}KXl}?KY(I}G1`-#sU ze(*EHCsmvv0IkO)NaZarUIYZ(iyhT0p73qjdCy`4w<)9^cZ#wiPw<=;KDw;e?(P|d z)AM?6$f=m`6{F@k4Wpr6*MG0uM$64IPhGUN-!lqJ>2uyCHqJ6P%8wIh&@WKC8>FF9q5U-Z_jiSrmdm=s`f*izOl-WT zc850->zoOGc16tn9oPZo5j@5RndxwCt_kTrQhbwoM?K3^&uXKYROWf!_22AILf*$X z*{`6;IRVmWh)dw0{`cpNmP?-s`z=tv_?O&U&j>E)&z!X-#D$>yYz^Ai`9vG~~@@B~wW#!b>?9 zp}{33ekXF%8vlkY4R>0x#tr#%X!HZD7vki+a~y`*POFzwfA2ZO=DR=44II(EXzYNr-mUw* zf(M_9hDKaQ29zKt#>Zv8eFLlD`@r~rO3rtJf{$$EX>kK(;u1RzCikJE+``quK=TC@z9 zsTx_nmn54$FLZl%-@W*G?MT^V27Kk8=Xf6K_Luotv_%q|R~rlktMNNhi_)^;-sIK@ z0#@ec%wKvOr3APAl7&0q363FrGokQaSpu(2H8kDa@LhIA%uzM}*@ihdI6~PU;~ks~ z|I~btzT5I6Rat%}fbBk*3V2oH&PRE07lsaGk`3{utIlov)l#Z=5N#N90+NCM$U#3G z+})2RbL{395M&&C3k_9N=P1Yx`GZx-z7BmGquNJfZfV-^L-_gjdRIPHw)KXvlgBLx zDHJlGXN(imeXdZP&N#uZ>3>0u)2^bl#JHJ;?5w2><1n7_)nI;~?^Q(PyOF-90?=WM#6`fcBx3il5xzy=W=jmNB1q8NK^TdN zOD#bLn$WPC`PjFLG%p2m9r5UjR@P&%q_~}*aeplxRi?hPNXa2@Z>^nw?zGldcoeY< z6(7RAJ6k-gAC#KFv7hR!yJ9iTtkdlx(cw ztMuOBWtm=T)1X=CuiwKZXo%N|)JMnaGT%<{Bma~gAOF@`Fh`A3qpk`S|Gj3dw7a{T zQxg$P-U7&4N+)zcQkzgDUX=M&qxJB^X`fzGlXKzl7GJyTaFvq(ZUu@qsqZX7Nhfh;UwjCnH0Iez9sT}g&^}>PgeT*y)Cj46$MpZa(-^U z1WntQP7=G&T_Y#)&_l2?&bNS}Jx9lDs&L&`i3_AZ~%WPQUn0<$6X`=FJAL9xQ6Rw`oJTcDwVYvwD#!%ob0ON@;|nx zh1fb?r+rD^p8~+GzPKjt(k(n82uE|1-b1?kvp z-05>MG>YH{0GEgIb|6!YvdW^7(2T4Hhem%IM>b8$G1qWE3ZHNH1>{e!$NyvKxLlqc znt9Cf5m5eq=aiOyZHNVA(d_redEO1L)aTPd# zm|>FXwzq6irPbjL0fbqQaLxlW7zTD)N-fY~E!W$P>RPN#@@_)Vt#@8uTry{?!VZnURIPJrXo>e zXLyS{s8zKyZ@<(;G)_e<^Mvii?@&_S{$a80M0tv4%K??56kwc#(rzO1p+Iv}aa(=9 z@i$F0YsOf6398Iqgd6T)_Wb$kDGHxm06n#V6|SsgMx0J({on{oK-mqC>r*aZJl?aLa{KDa>|T!X!gLr~={v@Uj+`myb%* zKs3_X**Pq`C+j4{Yx4gm@uIW4uQhmY2JP$^;`iKE>qX>)+uT$VnY(OE7z2V27r3m+ zP#&k`F02^FVnC!N`@h{<+1qasea@dP7Ss=-xtl}m%d=S_BOi7paLoAhPxGep;n;T% zgP@>(Cf}P>5l~QlUmXXX;U9jUK>QrE5%XvbjiA&o0Z#|7;QN+bp+H#M2+GeD7u863 zE@HLYk2$CQA4J7fOOGU@1Scb!arWnK9t%SwrMIbsfcNz_N=7c>E>}UEF)6K1RTW7!Q+kP{jHo<+C?&sH* zno3PlYTHmB9~CB+r$Z1Dj;|yy59@mywK6+ff!{Hg) zU2zxE)WqS@ElEQ!N`$y?TZ_?H^X)c(;oGK4?0(+i5@$(w7QuyB<(Oc_C5ncTks-XK z!h?rI%eT+GtdA=zYj~gX40J-~MXt@RN)%_0iPTFNs0{1q9fL1P{O_q#NEe_ZU z_dL=iByx3XnX!7}BWT!g*9EzLJfXW3N$J!r-?3iooKfCNBq=?)xw(vS$w7R+q%&tRVnlfC z-b(foJXDmUB+7F}R3&gA*8u7^C1#-j18ME>v3SWWQ-Y1{t;AFnBq@HQw%(omw%qRNVfY_dqSzR2M)VF%7jO{c{DOfOcYYOl zZEr$<1%(TBZWS2(ZsK6X3-4u*Tw$)}b<=O0F=(Md8=g6z7pa^UkasUzp0p{vEq{+z zV8)MDe@5RcVz?=&lF>_R4a$OAa*4%ZWG@204@MLeG{0*q2^fL~w=!+s=SS{sZHo{! z1;<0YujMGlnY4s`?+GDX;ud9|@7ApsGt#4&69IRaXHuh78I>1DoT_iuZJJ15S&kMdWHULbP0c1=itz`4jbxKiGvG&tW-km z3~OpySy3(_{*9KJGQcwP4Lz5UKhIw!6_wI;3wWUf#U-+~FwmhX<)oT>AqizW|KJ1iuOt(5 ziUdmDx&7Z$x+w*jQ`oXM(6Y7GV6kp!=xEYeTtvUSp<PTds&e(}cB-P|0np zX4HB2K^Cj$Q=rxMX=+@OhJs3JY>W)NL|EX2 zIMopC-&_BG7TCCifVVBsy`Ll}*$ieRfwnPF2%kJ-8$zh0l zy(65k$ebl8BEC|=Ip_@#;0bnOKb_8a0|G`#$xuql;l}au>}+&Gg1m+X_;Fp`pv zH53;U^YSViQObgQaVl^Div~tV8-7SHH!7^S>6CfdR6}hem&@G@LkLe6*}~F*^W5$iUWoQ2%|Qz zO6w%{2OSW3w+{C>%Imr530&`+Sd`+|qmhSazzi}o(#2OsL7qZTxM?UZjm%V-N-TN`xP|Vmjnk)DwPEhlYwq?jr%jx^Bl5uHX9T^sLhb*5Z?yG^ z#&xspf1{k4W3}H2sUa1Mc&c@*k0SWPnTlv}c=FPlS6cfaLqlUl3-x;;)vXtz9M-Je zwAyoaBPD|$R@F-WAZh4O_mXa-gkV{Jzo=YdFv6T*$j8h~UaCa`Gz1}ilY1$(oI9v1 zGu2eJ1@Bwx+Z0$9dN~nCHzcLYZ}>_Hs0P!{aI&a?1~HX|@?TcmaN!~h3H`RP*yrU23qX0Z zj6@WJDedm!r-M#DVv7$ZUtc$r(CR@Plw{WP{r_ua_EeEe9hJx{}IL8#6PjD9CH)xdk~CodT>yS8a0YV1Uhm$C+%{OWmkXA-ZdL z3>Q7UHMk*z>Oj4l-6UbKC4WfFIjzK6#D~Nz9C34M#$_*w=;hv2+4Pu0>~@@@`ntNS z6gBWM7s!${1p@_Ix^?6(I-!wiwOVHHG0h7gfF=ppCH5fJqG-iM3={?{;1X~T2P@B7 z+#|l5w+SP|ZG>kK4;XT6afP5|4uY9D@;LWJ0E zDz}>51*Ijg(BgBpjo$xLRWR+>>FFu(O$LCA*#Mvon?1pbe=Mf82HzpDf(DJSDZoN+ zf^vY&N8b-bX1IRlg&{HI>=?DeLw8&kzSRx+)ngdyfzfA@UWa9h=btpRV;`Wi#Rfhd zXy;qpAIOsqpjKNK+n{piIarN_+VF;Fi;RtpO|4T4n$Jc;co-kMbnn|4%@GO|6>^Cf z#Jg;S;1d^ITu=V!YO})_w|%ImQDyU9Du8^taTyI`(8)_hfWhx>B8!iY7L6AW z`3RZx12l><=|yJ14!lfO3oLrDozxoanN+IK{@qeuJ{q7dFZ(a*5flVFbHrsZKe|h! zTHdmCIlP<-k^Pv?Xtw2soxVOjec6jH>*^!#vmIoSM5~^jl@XVg(qM8&8jelhV%aCb zi+2i8uV8V+5aWP9>NKDEEF~;U6ch0{YI;qQmNPD?vTmA+3hI)`*axW1jyQ3#_~NQa z)Ee6^KN#blUZy#VfK423qKfkJcNQ$$W|T~o$#?%={?LFJjHIs^@cYbpv04x6WA_dN zf~bpg#5>dc$`Wg+gO+9Th4VfWO;J77h{QL$a%jx(7cKeuQc`ZYPYh&TGk{zVr|(>^ z(Hbjmi6Gn2QL+xgQ_t5kvfJYTY~0%o4T~7<^YXASkK;+=3Lt#JA3plg#oSEgi3y+# z2+Rxn0$=cJ6W9xn(o+d)KWi@Q1!E_gj#DCdfVD`20v7^|sDr&JV8dv;Zo;@NV#)!k zbj82;1xVxs#j=BpR^7zM*4G~&qfXZ9^sWjAGF?5@G>&5>lhj>NuR&BV9ff_(c3V-F zaMgLFq9H-Ks^b{|AU>S>=o9gReZcmZ-0!pYCqj>tnw#SV|0mKY zo?Jh)+ldsG{fhx27Y;4g%|PJiZ!4>66<|VteSN+HB{Ha%O-&#>eSkfC=jhiC-H+Jh zgv|8Q6D|Nva<3%uXO{=?;5IPA%6t0E?$8A z3;%id>Fx~JGuYc&YHc7%fEUBi@q8H;244|S>55H6gnU2=x0L1mcnDDY@TpANc z_WH5f1UP<pH{cZmb~dMdAPHEjGl?P%AZxir`}}3z!@;4B1-i-~ zaul8_-`djh*s!3D&uRbJpRV~B04{)8%Zc&|_9P<><1ney6vbP((Q+2cj9UErnhvfL ze4S6Jz#(&dg0s8K>M14dWT0&1rDf$nl>KUYWP%m#@?ej8ou9!?WNu}}5C!q73JWU* zg)e2*h16@1wOjvOO3v^F>w9>^*f&8zza}SOkZ({XM!aD64<}@XQQA#zZEgTsYryEY z;ObA`cdOrTfz`3X{4>Z7An>cSX4``lo_e}&D#7`)U+3Cwrqk7%mI2-M`MYOfcH?pMU_tDZE6C|{HoUi zgmVc7?qZ6lgvkZ@#MSk#l2z~iA{Av}EetmCW;Q==;40#t12Gy(NJMR70(uoo^#Qa#sR2-g)1})|S5RT8mTJed-c` zHg2*c{!3hf$>HrzWMm|0An%=T_Nc>6u&!6@`KzS~<{3nw8-bnk|4P7#bQ+_1c6JDq z$TQ1HkJHH9H*X$S#3385^P_G~4bs?11k+1Q%(o_lG~2=CMFqxl^Iyg@S=VQ%;!HkM z6v(DCf|jV;Je228f(;O#_)h^;h|$c{E5zf-0gD0t?NMSPmfmGLI9veywZq5fMqsiW z@N!t$*wp38GT{XiwvB~^{LeXAxXapF%x#}1m-~Z;k4p37cPc9@!PlukiIU0dFrTVi z2A~tuaf;+oS&T;0J1|#!$Hi@Jnnj?=-ud|NNdYfUGc&V{j0_2YA7|I^eILUBiRbvH zJ6lnW-3y33!ytBsi+-eGENon-F@Tuy0jk}!O&i;^thiWCK}Y8m6w+N&7^AlCHqg`a xz-^Am^XQFfz?NjQiikF|CBKx@)M4OEl}W>xQL8!rJ%0g{{WWXIgS7T diff --git a/graph/adding-subtypes.md b/graph/adding-subtypes.md deleted file mode 100644 index ffc07627..00000000 --- a/graph/adding-subtypes.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: Adding subtypes -owner: chrispre ---- - -# Adding new subtypes - -Table of Contents - -- [Overview](#overview) -- [Background](#background) -- [Risks](#risks) -- [Mitigation](#mitigation) -- [Shielding clients](#shielding-clients) - -This article discusses the consequences of introducing a new sub-type in the Microsoft Graph schema for a type that are used in collections. - -A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes (see [modeling variants](Modeling-variants)). This allows to model collections of objects that have slightly different behavior. The common behavior is represented in the base type and the variations in a subtype, a concept very familiar from OO programming languages. It is straightforward to add a new subtype to the hierarchy with some consequences to the backwards compatibility as shown below. - -## Overview - -OData allows to design collections of entities (entity sets, multi valued navigation properties) with values of different types. Currently these different types have to be subtypes of a common base type (often an abstract type). In the current version of Microsoft Graph are many collections of items that represent slightly different things, variants of one concept. For example, the [managedAppPolicy](https://docs.microsoft.com/en-us/graph/api/resources/intune-mam-managedapppolicy?view=graph-rest-1.0) type represents a base type for a variety of platform specific policies including for example a windowsInformationProtection policy. And when sending a GET request to the URL `/deviceAppManagement/managedAppPolicies` a collection of a mix of the sub-types is returned. -Most prominent are the collections of type directoryObject, an abstract base entity type that is implemented by types like user, groups, devices, etc.. - -Even though OData has means to express these subtypes and adding new subtypes is syntactically a backwards compatible change, there are situations that impose some risk to break client applications. - -This article discusses the steps to ensure backwards compatibility of adding new subtypes that are potentially returned in collections. - -## Background - -For heterogeneous collections, OData ensures that the client is able to distinguish the different types of the element of an collections. So for example querying - -```HTTP -GET https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners -``` - -returns a collection where each element has an additional property `@odata.type` - -```JSON -{ - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#directoryObjects", - "value": [ - { - "@odata.type": "#microsoft.graph.user", - "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", - "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" - // ... - } - ] -} -``` - -Using the `@odata.type` property, the client code can decide how to deserialize the code, for example deciding the class used to create an object/instance. - -Clients must anticipate that new subtypes get introduced and write code to guard against these situations. If not handled appropriately, the existing types most likely don't have the properties to store the returned JSON properties and these values have to be dropped. At the same time properties of the existing types can't get a value assigned. But even in untyped client code, without some compensation, there is no code that "looks" at the unexpected properties and misses the expected ones. - -How to guard against and handle these situations is very specific to the client application and requires to understand the intended semantics of the types returned by the service. - -## Risks - -There are a few potential risks when new sub-types are introduced. They are all variants of a) the fact that syntactically, previously expected properties might be absent and the received properties are ignored and b) the semantic role that the existing and new subtype play are potentially changing. - -- De-serialization code might break because of missing properties in returned collection items. Even though property X was mandatory on all subtypes previously returned, the new subtype might not have this property and the client code needs to deal with that. - -- Client libraries for strongly typed language might ignore some of the values in the @odata.type property without further configuration and need to be configured to be able to pick the right (client) type to deserialize into. - -- In the client code it is reasonable to assume, based on existing running code, the "world" is exclusively described by the current subtypes. E.g. there is an understanding in Microsoft Graph's directory workload that there are two types of actors: users and servicePrincipals. Introducing a new subtype that can be an actor in directory (e.g. a device) would require a lot of clients to change to anticipate the presence of an object of that type and deal with it's own set of properties. In essence: originally each object was one of n types. After the change, an object can be neither of the n (because it is of the n+1's type). - -## Mitigation - -Following are some of the techniques to mitigate these situation. - -### Avoid overgeneralized base types - -If the abstract base type has many subtypes, it is quite likely that specific collection only ever contains a few subtypes. Since the type hierarchy is wide, there is probably some functionality or behavior that is only shared amongst a few but not all subtypes. That is ultimately explaining why some collection only contains some of the subtypes, the ones that share some behavior. - -A well-known example for this situation is the `directoryObject` type which has many sub-types and only one property, `id`. Collections like for example the `owners` property on a `group` is declared as `directoryObject` and in reality only `user`s and `servicePrincipal`s are added to this collection since these are informally the only actors modeled in directory. - -If one only focuses on the hierarchy, one could easily think it is straightforward to add a new subtype. What is necessary is the ensure that adding a new subtype doesn't change the semantic of the type hierarchy and the semantic of the property with it's implicit constraints. - -### Roll-out sequence - -Microsoft Graph does not return object from a workload that has a type that is not configured in current metadata. That leads to behaviors that is slightly different depending if the object is returned as part of a collection or is requested individually. - -If an object of an un-configured type is returned by the workload as part of a collection, the object just gets excluded from the collection and not returned. Microsoft Graph just doesn't know yet how to serialize the object. - -If an object of an un-configured type is requested directly via an entity set, for example in case of `directoryObject` and a request like `/v1.0/directoryObject/{guid}`, Microsoft Graph returns an empty method body (not a 404 Not Found). It is assumed that the object and it's Id can not be found anywhere in the system and it is safe to ignore it. And Microsoft Graph just doesn't know yet how to serialize the object. - -To make sure that the users do not get exposed to the second behavior, ensure that newly introduced entities are first known by graph before they get added to the collections. -The configuration allows Microsoft Graph to respond with the details/properties of the entities. Without that configuration Microsoft Graph returns no response body. -This leads necessarily to a two-step process of first introducing the entity type but not return them in any of the heterogeneous collections. And only after that returning them as items of collections. This can of be done in relatively rapid succession. - -This is often not a problem since for utterly new entity types, no collection every has items of that type. But if the workload has APIs beside Microsoft Graph, these entities might have been added to the collections through that API. - -### Allow time for testing - -Inform the clients about the change and allow them to test the changes in beta. Time is required implement the code necessary to deal with the new entity type, both in terms of de-serialization as well as integrating it into the rest of the application. - -### Communicate the change in semantics - -Even more importantly, it is necessary for the client developers to incorporate the new semantic into their application/service, even if the change is perceived small. The addition of new data needs design changes in the client application. These changes potentially ripple through many layers of that application/service. This requires early communication and clear documentation what the new type represents and why/how it is considered a subtype of the original abstract type of the collection. Without that information the client application will not be able to process that data returned in the responses. - -For example lets assume a situation where owners of a group are people and the only type ever returned as a member of the `owners` property of the `group` entity type is of type `user`. It is reasonable to assume that certain behaviors/functionality exists for these members of the owners collection. For example, every owner has an email address, every owner has a manager. By introducing new types of items to this collection, these assumption might not be true anymore (e.g. owners can be machine accounts without a manager or email). Even though the protocol and client libraries have ways to deal with the transport and de-serialization of these new types, it requires some new design how downstream modules of the client applications/services deal with entities that don't have email addresses or managers. - -## Shielding Clients - -[TODO: describe upcoming features in Microsoft Graph to ensure full backwards compatibility] diff --git a/graph/deltas.md b/graph/deltas.md deleted file mode 100644 index dd5a9653..00000000 --- a/graph/deltas.md +++ /dev/null @@ -1,189 +0,0 @@ -[[_TOC_]] - -# Adding support for delta queries - -Delta query enables application to discover newly created, updated, or deleted entities without performing a full read of the target resource with every request. Microsoft Graph applications can use delta query to efficiently synchronize changes with a local data store. For an overview of the general concept, please [refer to the public documentation](https://docs.microsoft.com/en-us/graph/delta-query-overview). - -## Why should you add delta query support for your entities? - -There are different scenarios where customers are looking at syncing data to a separate system and/or tracking changes in a non-lossy way (making sure they are not missing any changes). These scenarios include compliance solutions, DLP solutions, apps that need to support offline usage and many more. -Today, if your API surface does not support delta queries, the only avenue for customers to implement such scenarios is to **continuously query your API surface**. This increases the complexity and cost of such solutions for customers or makes implementing certain scenarios impossible at scale. More importantly, it greatly increases COGS for the Microsoft Graph as well as your API. - -The Microsoft Identity Platform (AAD) has implemented delta query support for a majority of its entities and will continue to deliver more delta query support in an effort to provide a better experience for customers but also to **decrease COGS**. - -## How to add support for delta queries in your API - -### Create a new API onboarding review item - -Because you'll edit the API schema, you need to go through API review. Go ahead and create [an API review work item](https://microsoftgraph.visualstudio.com/onboarding/_workitems/create/API%20Review). - -> Note: if you are adding a net new API with Delta query support on day one, you can reuse the existing API onboarding review item so long as the delta query support was included in the initial API review - -### Update the public documentation - -Both the API review process and the API schema modification process will require you to provide a link for documentation update before allowing your changes to be added. By creating the public documentation ahead of time, you're making sure you have the required elements ahead of time and won't be blocked. -There are a few places where the reference of a new delta query support must be inserted. - -1. You need to add your resource to the [table of supported resources](https://docs.microsoft.com/en-us/graph/delta-query-overview#supported-resources) -1. You need to state that your resource supports delta query in the abstract, e.g. [orgContact](https://docs.microsoft.com/en-us/graph/api/resources/orgcontact?view=graph-rest-1.0). (\*) -1. You need to add a delta query support page for the resource in the api reference e.g. [orgContact delta](https://docs.microsoft.com/en-us/graph/api/orgcontact-delta?view=graph-rest-1.0&tabs=http). (\*) -1. You need to add reference to any page you added in the corresponding Table Of Content. -1. You need to add an entry for each version/entity that supports Delta queries to the [changelog](https://docs.microsoft.com/en-us/graph/changelog), [guidance](../../Document/Guidelines/Changelog) - -> **Note**: These pages are available for beta and v1.0, make sure you update the beta pages during the public preview of change notifications support for your API. Make sure you update v1.0 pages when support ships for general availability. Updates for different versions can be done in different pull request. - -You can see an example of adding delta query support for both v1.0 and beta to the docs [here](https://github.com/microsoftgraph/microsoft-graph-docs/pull/7451). - -Once your documentation pull request is submitted, the PR must be labeled with "Do not merge" until the changes are in place on the service, and then change the label to "Ready to merge". You should keep the link to the pull request at hand, you'll need it for the next steps. - -Should you require assistance with the documentation process, you can contact the [docs V-Team](mailto:MSGraphDocsVteam@microsoft.com). - -### Submit your API for review - -Now that you have prerequisite items, you are ready to submit an [API review](../../Review/Create-an-API-proposal). Describe that you are adding support for delta query (function) for your API. - -> **Note**: if you are adding a net new API with Delta query support on day one, you can reuse the existing API onboarding review so long as the delta query support was included in the initial API review - -### Update the schema metadata - -Once your API review has been completed, you are ready to publish the schema changes to indicate your Entity Type support delta queries. - -For this step you'll need: - -- The link to the API review work item previously created -- The link to the API review pull request previously completed -- The link to the documentation pull request (you can use the same link for docs and changelog changes) - -In your schema file you need to add the new delta function. Here is an example for the orgContact entity. (this needs to be added as a child of the Schema tag) - -```xml - - - - -``` - -In your entity declaration, you need to add an annotation stating that the entity supports delta query. (this needs to be added as a child of the EntityType tag) - -```xml - - - - - -``` - -You can now submit the schema changes following the [guidance](../../../Rollout/Publish-schema/Test-using-VSTS-repo). - -> Note: if you want the delta capability to be hidden from publicly available APIs for testing reasons, you can leverage [Privileged identities](../../../Rollout/Publish-schema/Publish-a-test-endpoint/Privileged-API) by setting the `ags:IsHidden="true"` attribute on both the annotation and the function. - -### Provide required information to support - -In order to provide proper support for our customers, the support teams need information you need to provide them with. - -This information will be provided by starting a separate process owned by support called SPOT. To start the process, [create a new intake](https://microsoftspot.azurewebsites.net/Intake). A release manager will then contact you and guide you through the process of collecting and documenting the required information for support teams. - -When creating the SPOT intake, make sure you indicate you are adding delta query support for your workload and set the following fields: - -- Disclosure level: No restrictions -- Release Type: Product/Service/Program -- Release Sub Type: Feature -- Responsible Org: Deployment Services -- Responsible Team: DS C+AI Team - -In relevant links, add the [delta query support wiki](https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/311338/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks) `https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/311338/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks` - -Some of the information that support will require to add it to their internal documentation include: - -- The ICM service and team for escalation -- The owning team (distribution list) - -### Implement query response - -You need to implement a query response to the `/microsoft.graph.delta` (also aliased `/delta`) requests that the Aggregator Gateway Service (AGS, the service immediately behind graph.microsoft.com) will forward to your workload's API. - -> The delta query endpoint should match the following pattern to avoid requiring code changes for request transformation in the AGS: `/{version}/entity/delta`. -> The delta query endpoint should authorize on the same permissions required to enumerate the entity type. - -The [OData ASP.NET](https://www.nuget.org/packages/Microsoft.AspNet.WebApi.OData/) and [OData ASP.NET core](https://www.nuget.org/packages/Microsoft.AspNetCore.OData) provide base controllers, serialization wrappers and more that help you generate the delta query response as shown [in the public documentation](https://docs.microsoft.com/en-us/odata/webapi/deltafeed_support). - -### Update routing information - -If your delta query implementation does not live on the same service (FQDN) as your entity's API, you need to update the endpoint routing configuration to account for it. For more information, see [Gradual Configuration Rollout ACIS Operations](../../../Rollout/Publish-config/Gradual-config-rollout-ACIS-operations)/Gradual-config-rollout-ACIS-operations). - -You also need to update your workload configuration to indicate your support the delta sync protocol e.g.: - -```xml - - - - - - - - -``` - -### Handle delta and skip tokens - -#### Skip tokens - -Skip tokens allow you to implement pagination for the response result to avoid returning too many results at once potentially leading to long response times. - -You must return a @odata.nextLink property with the response object if the enumeration contains more elements than requested (when \$top is provided by the customer, or default number of changes to return). - -The nextLink is a string that can contain any information you choose and it should contain a deltaLink that represents the starting point of the initial request as well as information to skip elements that have already been enumerated. - -When a nextLink is returned, you must not return a deltaLink. The nextLink will be encoded and prefixed by the public Microsoft Graph URL automatically by the AGS before being returned to the client. The client will use the public nextLink to: - -1. Know more changes must be enumerated. -1. Query the next changes. - -When querying the public nextLink, the AGS will relay the nextLink (value of \$skiptoken query parameter) that was provided by the workload and provide the value as a query parameter named `nextLink` to the workload when forwarding the request. - -When all changes are enumerated, a @odata.deltaLink property should be attached to the response object. - -#### Delta tokens - -Delta tokens allow you to watermark the last change seen by client so you can know exactly where the client left off on the change feed. You can then present to the client on their next request the changes that haven't already been seen. Under no circumstances should a client be missing changes between two delta requests (no gaps). - -If a response to a delta query request is returning the last change available at the time, a @odata.deltaLink property must be added to the response object. The deltaLink is a string that can contain any information you choose and it should allow you to precisely identify the last change seen by the client. - -When a deltalink is returned, you must not return a nextLink. It signals to the client that all the current changes have been enumerated and that they should query back, with the deltaLink, at a later time. The deltaLink will be encoded and prefixed by the public Microsoft Graph URL automatically by the AGS before being returned to the client. - -When querying the public deltaLink, the AGS will decode the deltaLink (value of \$deltatoken query parameter) that was provided by the workload and provide the decoded value as a query parameter named `deltaLink` to the workload when forwarding the request. - -> Delta links and next links should not exceed 10k characters to avoid routing issues at the AGS level. - -### Handle filter, top and select query parameters - -You should consider how these OData query parameters may or may not be supported for optimizing the response: - -- **\$select**: you **must** support the select query parameter to client applications to filter which properties they'd like to get in the response. -- **\$filter**: you may support the filter query parameter to allow client applications to filter which objects they'd like to get from the response. -- **\$top**: you may support the top query parameter to allow client applications to customize the number of results they'd like to get per page in the response. (see nextLink) -- **\$expand**: you may support the expand query parameter to allow client applications to get additional linked entities they get in the response. -- **\$orderby**: you may support the orderby query parameter to allow client applications to customize the order of the results they get in the response. -- **\$skip**: you should **not** support the skip query parameter as it's behavior might conflict with nextLink behavior already mandated by delta query. -- **\$count**: you may support the count query parameter to allow clients applications to get the count of items in the response along with the results. -- **\$search**: you may support the search query parameter to allow clients applications to filter which objects they'd like to get from the response. -- **\$format**: you should **not** support the format query parameter as the AGS is doing some data parsing and replacement before returning the response to clients applications and supports limited formats (JSON). - -> If you are building your API using ASP.NET MVC or ASP.NET core MVC you can leverage the OData library to parse and apply oData query parameters to the delta feed. Make sure you [configure your service pipeline and add the enablequery tag](https://docs.microsoft.com/en-us/odata/webapi/first-odata-api). The library also allows you to [advertise non-support of some query parameters on object properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnet.odata.query.nonfilterableattribute?view=odata-aspnetcore-7.0) via attributes. -> If you want more control over OData query options, you can additionally leverage the [ODataQueryOptions object](https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#invoking-query-options-directly). -> OData query parameters will be added in the encoded delta/skip token provided to the client application so they do not have to add it to each request. The parameters will be decoded and provided to your workload API by the AGS in any subsequent request. Updating the OData query parameters is not supported after an initial delta/skip token has been generated. The parameters stay consistent over time or the client application must restart the synchronization from scratch with the new parameters (querying delta API with no delta/skip token). - -## How to get help - -Should you need any help during your design and implementation, there are a couple of ways you can reach out: - -- [Stackoverflow.com](https://stackoverflow.com): for any question that does not contain confidential, internal or customer related information. Example: questions about ASP.NET core MVC, questions about the OData libraries, etc. -- [Internal StackOverflow](https://stackoverflow.microsoft.com): for any question that might contain confidential or internal information. Example: how do I configure the routing in AGS to do... -- [Teams: Microsoft Graph > Delta query](https://teams.microsoft.com/l/channel/19%3a32dabdaf736a4c9482fc3d96967b2ea8%40thread.skype/Delta%2520query?groupId=6d279915-6f5d-452c-895b-7f4f82038843&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47): for any question that contains details really specific to your implementation, escalation of questions left unanswered on one of the StackOverflow platforms, ... - -Ask: please refrain from asking questions directly to the engineering team via either emails, Teams chat etc... This does not scale and it doesn't capture the question (and answer) for other people that might have the same question as you. You should always try to ask a question on stack overflow first before reaching out on the Teams channel. - -## End to end testing - -You can use Canary and PPE for end to end testing of the implementation as outlined in [the following documentation](../../../Rollout/Publish-schema/Test-using-VSTS-repo)/Rollout/Final-steps/Test-using-VSTS-repo). -Private previews can be done by creating a _new version_ of Microsoft Graph as outlined in the documentation previously linked. diff --git a/graph/entity-complex.md b/graph/entity-complex.md deleted file mode 100644 index 477cf003..00000000 --- a/graph/entity-complex.md +++ /dev/null @@ -1,58 +0,0 @@ -# Entity Types and Complex Types - -Microsoft Graph uses entities and complex types as way to describe the structure of the requests and responses of an API. - -Entity Types and Complex Types have some similarities and some differences that we are going to explore in this article. - -## Entity Types - -Entity Types are that most common way to define the structure of the requests and responses of a Graph service. - -> Entity Types are nominal structured types with a key that consists of one or more references to structural properties. An entity type is the template for an entity: any uniquely identifiable record such as a customer or order.. - -There is three things to note in that (arguably terse) definition: - -- By "structured" the standard means that an Entity Type is defined by enumerating its (typed) **properties**. -- "nominal" just refers to the fact that the type **has a name**. -- And the most important piece in the context of this article is that an Entity Type declares a **key property** and the consequence is that objects of this type can be uniquely identified through this key. In Microsoft Graph that key is currently always the property named "id". The standards allows more variation and Microsoft Graph might also relax this constraint in the future. - -How they key is used to identify an object is a bit out of the scope of this document. For now it should suffice to say, that it is used as part of the URL to "name" an individual object. For Example in the URL https://localhost/api/authors/50 (or https://localhost/api/authors(50) ), the 50 is the key of an object. - -## Complex Types - -Complex Types are non-scalar properties of entity types that enable scalar properties to be organized within entities. Complex types consist of a list of properties with no key, and can therefore only exist as properties of a containing entity. You can use complex types to group fields together without exposing them as an independent OData entity. Complex types can contain complex types, that is, they can be deeply nested. - -- A complex type doesn't have keys and therefore cannot exist independently. -- Complex type can only exist as properties of entity types or other complex types. -- As of 9/2020, navigation properties within complex types are supported in Microsoft Graph (with some limitations, such as: cannot be composed with operations, and structural properties cannot be used within `$expand` paths). - -## Comparison - -In the example below, we have added an Author as an Entity Type and Address a Complex Type. - -```XML - - - - - - - - - - - - - - -``` - -You can see that Address type does not have any sort of key property. Complex types cannot be tracked on their own, so as a property in the Author class, it will be **tracked as part of** an author object. The consequence is that its life cycle is coupled to the enclosing Entity Type: When the author gets deleted, the address gets deleted as well. - -## Summary - -In Summary: - -- Both Entity Types and Complex types are named types that declare a list of properties for the objects of that type. -- An Entity Type always has a key declared whereas a Complex type doesn't. -- Objects of an Entity Type can be directly addressed via an URL but ComplexTypes are always contained in an EntityType object and can only be addressed through a combination of an Entity address and a property name. diff --git a/graph/evolvable-enums.md b/graph/evolvable-enums.md deleted file mode 100644 index 7c28b6ea..00000000 --- a/graph/evolvable-enums.md +++ /dev/null @@ -1,104 +0,0 @@ -[[_TOC_]] - -# Adding Members to Enumerations - -Microsoft Graph services sometimes want to add a member to an enumeration type. However, there are barriers. First and foremost, some deserializers (including Json.NET) fail if an enumeration property has a value not found in that property's enumeration type. Second, a client may not deal with enumeration values unknown to it. The `Evolvable Enumerations` pattern and implementation allows a member to be safely be added to an enumeration. - -## Evolvable Enumerations - -An evolvable enumeration contains the sentinel member `unknownFutureValue` after which new enumeration members are added. Consider the following enumeration: - -```xml - - - - ... - - - -``` - -From this the C# SDK generates: - -```csharp -public enum weekday -{ - monday, - tuesday, - ... - sunday, - unknownFutureValue -} -``` - -The new enumeration member `newday` is added after `unknownFutureValue`: - -```xml - - - - ... - - - - -``` - -From this the C# SDK generates: - -```csharp -public enum weekday -{ - monday, - tuesday, - ... - sunday, - unknownFutureValue, - newday // new value -} -``` - -## Methods and client opt-in - -On POST, if any enumeration property of the entity contains `unknownFutureValue`, the request will fail with `400 Bad Request`. On PATCH, any enumeration property with value `unknownFutureValue` is ignored--that property is not updated. - -Callers signal their ability to process added members by including the `include-unknown-enum-members` preference: - -```http -GET /me/calendar -Prefer: include-unknown-enum-members -``` - -Upon GET, when this header is absent, `unknownFutureValue` is returned to the caller for enumeration property values that are one of the added enumeration members. When this header is present, the enumeration value is returned unchanged. - -Upon a filtered GET, when this header is absent, if `unknownFutureValue` appears in a `$filter` clause it matches any added enumeration member. For example, if the enumeration members `newday` and `anotherNewDay` have been added to `weekday`, these are equivalent: - -```http -$filter=weekday eq unknownFutureValue -$filter=weekday eq newday or weekday eq anotherNewDay -$filter=weekday ge unknownFutureValue -``` - -If the header is absent, `$filter=weekday` **eq** `unknownFutureValue` matches any new enumeration value. If the header is present, that same filter matches nothing, while `$filter=weekday` **ge** `unknownFutureValue` matches any new enumeration value. (Note: the latter matches new enumeration values whether the header is present or not.) - -## SDK code generation - -The SDK generates enumeration definitions from the current schema. Requests always include the `include-unknown-enum-members` header. - -At runtime, since other members could have been added to the enumeration after code was generated, values unknown to the generated code are translated to `unknownFutureValue`. When the service sees an enumeration property with the value `unknownFutureValue`, it will ignore it and not update the property. - -## Resetting an Evolvable Enumeration - -Upon a major version change, `unknownFutureValue` can be moved to the end of the enumeration, making known the previously unknown enumeration members. - -```csharp -public enum weekday -{ - monday, - ... - sunday, - newday, - anotherNewDay, - unknownFutureValue -} -``` diff --git a/graph/long-running-operations.md b/graph/long-running-operations.md deleted file mode 100644 index f063d4ad..00000000 --- a/graph/long-running-operations.md +++ /dev/null @@ -1,328 +0,0 @@ -# Long-running operations - -Long running operations, sometimes called async operations, tend to mean different things to different people. This section sets forth guidance around different types of long running operations, and describes the wire protocols and best practices for these types of operations. - -1. One or more clients MUST be able to monitor and operate on the same resource at the same time. -2. The state of the system SHOULD be discoverable and testable at all times. Clients SHOULD be able to determine the system state even if the operation tracking resource is no longer active. The act of querying the state of a long running operation should itself leverage principles of the web. i.e. well defined resources with uniform interface semantics. Clients MAY issue a GET on some resource to determine the state of a long running operation -3. Long running operations SHOULD work for clients looking to "Fire and Forget" and for clients looking to actively monitor and act upon results. -4. Cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a canceled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. - -## Resource based long running operations (RELO) - -Resource based modeling is where the status of an operation is encoded in the resource and the wire protocol used is the standard synchronous protocol. In this model state transitions are well defined and goal states are similarly defined. - -_This is the preferred model for long running operations and should be used wherever possible_. Avoiding the complexity and mechanics of the LRO Wire Protocol makes things simpler for our users and tooling chain. - -An example may be a machine reboot, where the operation itself completes synchronously but the GET operation on the virtual machine resource would have a "state: Rebooting", "state: Running" that could be queried at any time. - -This model MAY integrate Push Notifications. - -While most operations are likely to be POST semantics, In addition to POST semantics services MAY support PUT semantics via routing to simplify their APIs. For example, a user that wants to create a database named "db1" could call: - -```http -PUT https://api.contoso.com/v1.0/databases/db1 -``` - -In this scenario the databases segment is processing the PUT operation. - -Services MAY also use the hybrid defined below. - -## Stepwise long running operations - -A stepwise operation is one that takes a long, and often unpredictable, length of time to complete, and doesn't offer state transition modeled in the resource. This section outlines the approach that services should use to expose such long running operations. - -Service MAY expose stepwise operations. - -> Stepwise Long Running Operations are sometimes called "Async" operations. This causes confusion, as it mixes elements of platforms ("Async / await", "promises", "futures") with elements of API operation. This document uses the term "Stepwise Long Running Operation" or often just "Stepwise Operation" to avoid confusion over the word "Async". - -Services MUST perform as much synchronous validation as practical on stepwise requests. Services MUST prioritize returning errors in a synchronous way, with the goal of having only "Valid" operations processed using the long running operation wire protocol. - -For an API that's defined as a Stepwise Long Running Operation the service MUST go through the Stepwise Long Running Operation flow even if the operation can be completed immediately. In other words, APIs must adopt and stick with a LRO pattern and not change patterns based on circumstance. - -### `PUT` - -Services MAY enable PUT requests for entity creation. - -```http -PUT https://api.contoso.com/v1.0/databases/db1 -``` - -In this scenario the _databases_ segment is processing the PUT operation. - -```http -HTTP/1.1 202 Accepted -Location: https://api.contoso.com/v1.0/operations/123 -``` - -For services that need to return a partially created response here, use the hybrid flow described below. - -### `POST` - -Services MAY enable POST requests for entity creation. - -```http -POST https://api.contoso.com/v1.0/databases/ - -{ - "fileName": "someFile.db", - "color": "red" -} - -HTTP/1.1 202 Accepted -Location: https://api.contoso.com/v1.0/operations/123 -``` - -### `POST` hybrid model - -Services MAY respond synchronously to POST requests to collections that create a resource even if the resources aren't fully created when the response is generated. In order to use this pattern, the response MUST include a representation of the incomplete resource and an indication that it is incomplete. - -For example: - -```http -POST https://api.contoso.com/v1.0/databases/ HTTP/1.1 -Host: api.contoso.com -Content-Type: application/json -Accept: application/json - -{ - "fileName": "someFile.db", - "color": "red" -} -``` - -Service response says the database is being created at the URL provided in the Content-Location header, but indicates the request is not completed by including a 202 status code and Location header pointing to an operation resource. The response body includes the incomplete representation of the resource that will eventually exist at the URL in the Content-Location header. - -```http -HTTP/1.1 202 Accepted -Content-Location: https://api.contoso.com/v1.0/databases/db1 -Location: https://api.contoso.com/v1.0/operations/123 - -{ - "databaseName": "db1", - "color": "red", - "Status": "Provisioning", - [ … other fields for "database" …] -} -``` - -### Operations resource - -Services MAY provide an `/operations` resource at the tenant level. - -Services that provide the `/operations` resource MUST provide GET semantics. GET MUST enumerate the set of operations, following standard pagination, sorting, and filtering semantics. The default sort order for this operation MUST be: - -| Primary Sort | Secondary Sort | -| ---------------------- | ----------------------- | -| Not Started Operations | Operation Creation Time | -| Running Operations | Operation Creation Time | -| Completed Operations | Operation Creation Time | - -> TIP
    -> **Note:** that "Completed Operations" is a goal state (see below), and may actually be any of several different states such as "successful", "canceled", "failed" and so forth. - -### Operation resource - -An operation is a user addressable resource that tracks a stepwise long running operation. Operations MUST support GET semantics. The GET operation against an operation MUST return: -1.The operation resource, it's state, and any extended state relevant to the particular API. -2.200 OK as the response code. - -Services MAY support operation cancellation by exposing DELETE on the operation. If supported DELETE operations MUST be idempotent. - -> TIP
    -> **Note:** From an API design perspective, cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a canceled operation It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. - -Services that do not support operation cancellation MUST return a 405 Method Not Allowed in the event of a DELETE. - -Operations MUST support the following states: - -1. NotStarted -2. Running -3. Succeeded. Terminal State. -4. Failed. Terminal State. - -Services MAY add additional states, such as "Canceled" or "Partially Completed". Services that support cancellation MUST sufficiently describe their cancellation such that the state of the system can be accurately determined and any compensating actions may be run. - -Services that support additional states should consider this list of canonical names and avoid creating new names if possible: Canceling, Canceled, Aborting, Aborted, Tombstone, Deleting, Deleted. - -An operation MUST contain, and provide in the GET response, the following information: - -1. The timestamp when the operation was created. -2. A timestamp for when the current state was entered. -3. The operation state (notstarted / running / completed). - -Services MAY add additional, API specific, fields into the operation. The operation status JSON returned looks like: - -```json -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "lastActionDateTime": "2015-06-19T12-01-03.45Z", - "status": "notstarted | running | succeeded | failed" -} -``` - -### Percent complete - -Sometimes it is impossible for services to know with any accuracy when an operation will complete. Which makes using the Retry-After header problematic. In that case, services MAY include, in the operationStatus JSON, a percent complete field. - -```json -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "percentComplete": "50", - "status": "running" -} -``` - -In this example the server has indicated to the client that the long running operation is 50% complete. - -### Target resource location - -For operations that result in, or manipulate, a resource the service MUST include the target resource location in the status upon operation completion. - -```json -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "lastActionDateTime": "2015-06-19T12-06-03.0024Z", - "status": "succeeded", - "resourceLocation": "https://api.contoso.com/v1.0/databases/db1" -} -``` - -### Operation tombstones - -Services MAY choose to support tombstoned operations. Services MAY choose to delete tombstones after a service defined period of time. - -### The typical flow, polling - -1. Client invokes a stepwise operation by invoking an action using POST -2. The server MUST indicate the request has been started by responding with a 202 Accepted status code. The response SHOULD include the location header containing a URL that the client should poll for the results after waiting the number of seconds specified in the Retry-After header. -3. Client polls the location until receiving a response that indicates the stepwise long running operation is complete. - -### Example of the typical flow, polling - -Client invokes the restart action: - -```http -POST https://api.contoso.com/v1.0/databases HTTP/1.1 -Accept: application/json - -{ - "fromFile": "myFile.db", - "color": "red" -} -``` - -The server response indicates the request has been created. - -```http -HTTP/1.1 202 Accepted -Location: https://api.contoso.com/v1.0/operations/123 -``` - -Client waits for a period of time then invokes another request to try to get the operation status. - -```http -GET https://api.contoso.com/v1.0/operations/123 -Accept: application/json -``` - -Server responds that results are still not ready and optionally provides a recommendation to wait 30 seconds. - -```http -HTTP/1.1 200 OK -Retry-After: 30 - -{ - "createdDateTime": "2015-06-19T12-01-03.4Z", - "status": "running" -} -``` - -Client waits the recommended 30 seconds and then invokes another request to get the results of the operation. - -```http -GET https://api.contoso.com/v1.0/operations/123 -Accept: application/json -``` - -Server responds with a "status:succeeded" operation that includes the resource location. - -```http -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "lastActionDateTime": "2015-06-19T12-06-03.0024Z", - "status": "succeeded", - "resourceLocation": "https://api.contoso.com/v1.0/databases/db1" -} -``` - -### The typical flow, push notifications - -1. Client invokes a long running operation by invoking an action using POST. The client has a push notification already setup on the parent resource. -2. The service indicates the request has been started by responding with a 202 Accepted status code. The client ignores everything else. -3. Upon completion of the overall operation the service pushes a notification via the subscription on the parent resource. -4. The client retrieves the operation result via the resource URL. - -### Example of the typical flow, push notifications existing subscription - -Client invokes the backup action. The client already has a push notification subscription setup for db1. - -```http -POST https://api.contoso.com/v1.0/databases/db1?backup HTTP/1.1 -Accept: application/json -``` - -The server response indicates the request has been accepted. - -```http -HTTP/1.1 202 Accepted -Location: https://api.contoso.com/v1.0/operations/123 -``` - -The caller ignores all the headers in the return. - -The target URL receives a push notification when the operation is complete. - -```http -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "value": [ - { - "subscriptionId": "1234-5678-1111-2222", - "context": "subscription context that was specified at setup", - "resourceUrl": "https://api.contoso.com/v1.0/databases/db1", - "userId" : "contoso.com/user@contoso.com", - "tenantId" : "contoso.com" - } - ] -} -``` - -### `Retry-After` - -In the examples above the Retry-After header indicates the number of seconds that the client should wait before trying to get the result from the URL identified by the location header. - -The HTTP specification allows the Retry-After header to alternatively specify a HTTP date, so clients should be prepared to handle this as well. - -```http -HTTP/1.1 202 Accepted -Location: http://api.contoso.com/v1.0/operations/123 -Retry-After: 60 -``` - -> TIP
    -> **Note:** The use of the HTTP Date is inconsistent with the use of ISO 8601 Date Format used throughout this document, but is explicitly defined by the HTTP standard in [RFC 7231][rfc-7231-7-1-1-1]. Services SHOULD prefer the integer number of seconds (in decimal) format over the HTTP date format. - -## Retention policy for operation results - -In some situations, the result of a long running operation is not a resource that can be addressed. For example, if you invoke a long running Action that returns a Boolean (rather than a resource). In these situations, the Location header points to a place where the Boolean result can be retrieved. - -Which begs the question: "How long should operation results be retained?" - -A recommended minimum retention time is 24 hours. - -Operations SHOULD transition to "tombstone" for an additional period of time prior to being purged from the system diff --git a/graph/omitting-properties.md b/graph/omitting-properties.md deleted file mode 100644 index a2ffa174..00000000 --- a/graph/omitting-properties.md +++ /dev/null @@ -1,103 +0,0 @@ -# Omitting properties - -There are scenarios where the server contains business logic that determines if a property value should be returned, or not, to the client. Even when the client explicitly requests the property, it may be purposefully omitted from the response by the server. This section provides guidance on how the server should explicitly represent omitted properties in the response. - -> TIP
    -> In such scenarios, the property is publicly known and published through the schema. Clients are aware of the property and they can request it, but the server decides to not return it. - -## Scenarios - -These are scenarios existing today where omitting properties is desirable: - -- The property value is only available in tenants that subscribe to a specific level of paid product, such as Azure AD Premium P2 or Microsoft 365 E5. -- The client is calling with permissions that are not sufficient to view the property. For example, `User.ReadBasic.All` allows access to only the basic properties of the `User` entity. -- The client is calling on behalf of a user whose relationship to the target entity allows limited access. For example, a teacher is accessing student data, but the student is not in the teacher's classroom, so limited properties of the student entity should be returned. - -## `omitted` annotation - -Returning `null` values or implicitly hiding properties creates ambiguity and does not promote correct app logic. Instead, when the server decides to omit property values, it should explicitly state that properties are omitted in the response. - -When a property is omitted, it should still be returned with a `null` value; this allows client code that expects the property to work seamlessly and makes the handling of the annotations optional. - -In addition to the null property, a corresponding annotation should be included using the following format: - -```json -"{propertyName}@omitted": -{ - "code": "{code}" -} -``` - -- `propertyName` matches the name of the original property. It is followed by the annotation `@omitted`. -- The value is a JSON payload with one property - `code` - whose value is one of the predefined "reason codes". This value can be interpreted programatically, and it is also human-readable. - -App code can interpret this portion of the response to pivot its business logic. For example, a developer can create a multi-tenant application that works in all tenants, even ones that never have access to certain property values; the app can detect when values are omitted and react as appropriate. - -### Response example - -An app is getting a resource that is a collection of `sampleEntity`. It selects properties 1 to 3. - -```http -GET -https://graph.microsoft.com/beta/sampleEntities?$select=id,property1,property2,property3 -``` - -Let's assume that in the tenant in which this request is made, `property3` is not available because the tenant does not have the required product license. The response would look as follows: - -```http -{ - "@odata.context": "https://graph.microsoft-ppe.com/v1.0/$metadata#sampleEntities", - "value": [ - { - "id": "guidA", - "property1": "valueA-1", - "property2": "valueA-2", - "property3": null, - "property3@omitted": { - "code": "licensedProductRequired" - } - }, - { - "id": "guidB", - "property1": "valueB-1", - "property2": "valueB-2", - "property3": null, - "property3@omitted": { - "code": "licensedProductRequired" - } - }, - ] -} -``` - -### Code values - -Code values should be well defined and documented. There should be a unique code for each major scenario that is common across the entire Graph API surface. Teams should strive to align their case with more generic global scenarios. - -These are the code values based on existing scenarios exposed through Graph today: - -| Code | Scenario | -| ----------------------- | --------------------------------------------------------------------------------------------------------------------- | -| licensedProductRequired | A licensed product is required in the tenant, or a license must be assigned to the user whose data is being accessed. | -| limitedPermissions | The app or user permissions used to make the call are insufficient to access the specific property. | -| limitedRole | The role of the app or user in relation to the target entity is insufficient to access the specific property. | - -> TIP
    -> The difference between `limitedPermissions` and `limitedRole` is subtle, but important. The former will result in an omitted property for all entities in a collection. For example, using the `User.ReadBasic.All` permission will omit properties from **all** users returned. The latter may result in properties omitted for only some entities. For example, a teacher reading users in a school may see full properties for students in their classroom, while seeing limited properties for the rest of the students. - -### When to return annotations - -`@omitted` annotations should be returned when a property was in scope for the requests, but the server decided to omit it. For a property to be in scope means either of these two cases: - -- the request included the `$select` parameter that referenced the property -- or, the request did not include `$select` but the property would normally be included as part of the default property set for the entity (note: some workloads include a subset of properties by default, while others include all properties). - -## Callers opt-in to this behavior - -Unless the caller explicitly opts-in into omit annotations, the response should simply ignore the property and not include it in the response. Only when the specific header value is included in the request, should the behavior described above kick in. - -The header used to opt-in is as follows: - -```http -TBD - we have not finalized the header -``` diff --git a/graph/Modelling with Facets Pattern.md b/graph/patterns/Modelling with Facets Pattern.md similarity index 100% rename from graph/Modelling with Facets Pattern.md rename to graph/patterns/Modelling with Facets Pattern.md diff --git a/graph/Modelling with Subtypes Pattern.md b/graph/patterns/Modelling with Subtypes Pattern.md similarity index 100% rename from graph/Modelling with Subtypes Pattern.md rename to graph/patterns/Modelling with Subtypes Pattern.md diff --git a/graph/PatternDescriptionTemplate.md b/graph/patterns/PatternDescriptionTemplate.md similarity index 99% rename from graph/PatternDescriptionTemplate.md rename to graph/patterns/PatternDescriptionTemplate.md index 12ca1570..236aa2ff 100644 --- a/graph/PatternDescriptionTemplate.md +++ b/graph/patterns/PatternDescriptionTemplate.md @@ -7,6 +7,7 @@ Microsoft Graph API Design Pattern ### *Provide a short description of the pattern.*
    + ## Problem -------- *Describe business context relevant for the pattern.* diff --git a/graph/shared-types.md b/graph/shared-types.md deleted file mode 100644 index eda4cafc..00000000 --- a/graph/shared-types.md +++ /dev/null @@ -1,210 +0,0 @@ -# Overview - -This topic describes three shared types scenarios: - -1. Shared type object reuse (enums and complex types) -2. Extending existing resources (entity types) that another workload masters -3. Shared entity types - where different workloads want to share a common schema - -For each scenario we'll show you how to update your workload schema (with the requisite AGS annotated schema), with some examples. - -## Shared object reuse (enums and complex types) - -We encourage API owners to reuse types where possible - some examples of some common shared types being `keyValuePair` and `patternedRecurrence`. - -You can reuse any existing enum or complex type in your workload CSDL by declaring the full type definition in an identical manner to anywhere else it is declared. Any difference in the type's public declaration across workload CSDLs that define that same type will lead to (GMM) rule validation errors. NOTE: this does not include -workload specific annotations (such as ags:WorkloadName). If you want to change anything about these types (like change a complex type to be an open type or making a property nullable), you'll need to make that change in **all** workload CSDLs that declare that type, and then get sign-off from those workload owners. - -## Referencing and extending existing schema (entity types) - -You will frequently need to connect your models/APIs to other entities in Microsoft Graph. This is a key value of Microsoft Graph. For example, you might have some data or properties that you want to add and surface on another entity type that is mastered by another workload. An example of this might be to add a `signInActivity` (complex type) property to the existing `user` entity type. The data for `signInActivity` comes from **workload A**, while `user` is mastered by **DirectoryServices workload**. This "extending existing schema" is sometimes also referred to as a "composite type" where querying the entity returns data from multiple workloads (although we strive to hide that fact from the Microsoft Graph caller). - -> **NOTE**: You cannot extend [shared entity types](#shared-entity-types). - -### How to - -To enable this, you will have to provide reference versions of the entities you need in your own workload schema. - -- Add a declaration to your own workload schema for the entity type(s) you want to extend. This will live under your own namespace, so refer to it as though it was in your own namespace. -- Omit the `ags:IsMaster` annotation to indicate that your service does not master this entity type -- Do not express inheritance relationships between external entities -- Do not include a copy of the 'virtual' entity named `entity` - this is synthesized by Microsoft Graph itself -- Add your `ags:AddressUrl` for the endpoint that implements the entity type in your service. It should be added to an "entry point" in your schema. In this case, that's either on a singleton or entity set. In general, other entry points could be navigation properties, functions or actions. -- Extend the entity type with the set of properties that your service masters. These can be primitive type properties, complex type properties, navigation properties or even binding functions and actions. - -### Examples - -Here's an example for `signInActivity` taken from the Microsoft.AAD.Reporting.csdl workload, that adds a property to the `user` entity type. This property will show up in the user entity type definition in the [Microsoft Graph CSDL](https://graph.microsoft.com/beta/$metadata). For the sake of brevity the complex type definition for `signInActivity` is not shown: - -```xml - - - - - - - - -``` - -Here's another (abbreviated) example from the Microsoft.Exchange.csdl workload where a `messages` contained navigation property is added to the `user` entity type, to enable paths such as `https://graph.microsoft.com/v1.0/users/{id}/messages`. - -```xml - - - - - - -``` - -### Paging and filtering - -When extending existing entity types (with standard properties), for those properties to be returned, the caller **must** use `$select` (i.e. they do not return by default). The Microsoft Graph front-end (AGS) will send requests to all endpoints that contribute to the "composite type" and merge the result. - -To enable **paging** (and filtering), your service will need to implement **bulk fetch operation** support, typically in the form of an action that gets records by IDs, similar to what [Directory Services offers](https://docs.microsoft.com/graph/api/directoryobject-getbyids?view=graph-rest-1.0&tabs=http). The request body that AGS sends will contain an "ids" array property. This does not need to be a public action and can remain internal. Once you have this bulk fetch by IDs mechanism, you need to register it using workload service configuration: - -```xml - - - - - -``` - -You can see an example of this in [Microsoft.AAD.Reporting service config](https://microsoftgraph.visualstudio.com/onboarding/_git/AGS-OnboardingAutomationPipeline?path=%2FMicrosoft.AAD.Reporting_WorkloadConfig.config&version=GBconfig%2Fprd%2FringSMK&_a=contents). - -#### How this works - -> **NOTE**: Simply adding the BulkFetchUri config and implementing the bulk fetch operation lights up the following paging and filtering support. No further work is necessary. - -For a _standard paging request_ using `$select` - for example `GET https://graph.microsoft.com/beta/users?$select=id,displayName,signInActivity`- the first page of results is fetched from the workload that masters the entity type. The IDs from the response are used in the bulk fetch request to any other workloads that master properties in the $select. The results are stitched together and the page of merged results are returned to the caller (with a standard nextlink to the next page of results). You can see how this works using the `$whatif` query parameter - just append this to the GET request above, in [Graph Explorer](https://aka.ms/ge): - -```json -{ - "Description": "Foreach entity obtained from Request1, run Request2 and merge responses:", - "Request1": { - "Description": "Execute HTTP request", - "Uri": "https://graph.windows.net/v2/72f988bf-86f1-41af-91ab-2d7cd011db47/users?$select=id,displayName,id", - "HttpMethod": "GET", - "TargetWorkloadId": "Microsoft.DirectoryServices" - }, - "Request2": { - "Description": "Execute HTTP request", - "Uri": "https://reportingservice.activedirectory.windowsazure.com/auditLogs/userSignInActivity/Default.GetByIds?$select=signInActivity", - "HttpMethod": "GET", - "TargetWorkloadId": "Microsoft.AAD.Reporting" - } -} -``` - -If you want to offer _paging **and** filtering_, this is also possible - with a [limitation](#current-limitations). The order of operation is the same if filtering on properties from Directory Services (like `displayName`, `department` etc). However, if filtering on a property mastered by a different workload, the request goes there first. So for example, adding a filter clause on `signInActivity` and using our `$whatif` query parameter yields: - -```json -{ - "Description": "Foreach entity obtained from Request1, run Request2 and merge responses:", - "Request1": { - "Description": "Execute HTTP request", - "Uri": "https://reportingservice.activedirectory.windowsazure.com/users?$filter=signInActivity%2flastSignInDateTime+le+2020-06-01T00%3a00%3a00Z&$select=signInActivity,id", - "HttpMethod": "GET", - "TargetWorkloadId": "Microsoft.AAD.Reporting" - }, - "Request2": { - "Description": "Execute HTTP request", - "Uri": "https://graph.windows.net//v2/72f988bf-86f1-41af-91ab-2d7cd011db47/getObjectsById?$select=id,displayName", - "HttpMethod": "GET", - "TargetWorkloadId": "Microsoft.DirectoryServices" - } -} -``` - -Here you can see that the order of operation has changed, and in Request2 it's DirectoryServices that's called with it's bulk fetch mechanism. - -> **IMPORTANT**: To support filtering for properties mastered outside of the workload that masters the entity type, all workloads (including the master workload) that contribute to the "composite type" **must** support and be configured with a bulk fetch operation. - -### Current limitations - -1. Filtering on properties from multiple workloads is not supported. -2. Fan-out writes are not supported for composite types: - - POST to the entity type with properties defined in multiple workloads is not supported. Callers need to create the entity first (with properties from the master workload only), followed by a PATCH to add the other properties. **NOTE**: there may be delays before the PATCH operation will work (due to replication/sync delays). - - Similarly PATCH containing properties mastered by multiple workloads is also not supported. - -The fan-out write scenarios may be better supported using the [instant-on mechanism](../../../Service/Proxy/Instant-On). - -## Shared entity types - -For this scenario, API owners in different teams/workloads want to share a common set of schema, or entity types. This provides Microsoft Graph developers with consistent objects and experiences for functionality (that unbeknown to them) is spread across multiple services. Examples candidates for this scenario: - -* Common audit event entity type (or base type) -* M365 unified RBAC APIs has a common entity types for role definitions and role assignments, used by multiple teams (Azure AD, Intune and Exchange). - -### How to - -To implement shared types is pretty simple: - -* One workload CSDL defines the entity type (and all of its properties) -* The other workloads - * define the same entity type, but do not need to define its properties. This **is** different from shared complex types. - * must **not** define any additional properties in the type definition. You cannot extend a shared entity type. This would be counter to shared entity types being the same. -* All workloads (including the one that defines the type) mark the shared entity type as shareable - using the `ags:IsSharedEntity="true"` AGS annotation. -* Do **not** mark any of the shared entity types with `ags:IsMaster="true"` AGS annotation. - -The shared types can be used in their own workload like any other entity type: - -* in navigation properties -* have functions or actions bound to them -* used as a base class for derived types - -### Example - -We'll use the M365 unified RBAC APIs as an example. -Here the Enterprise RBAC workload defines the unified role definition and unified role assignment entity types together with its properties. Both Directory Services and Intune workload also define the same types but without its properties. - -**In Microsoft.EnterpriseRbac.csdl**: - -This workload **fully** defines the `unifiedRoleDefinition` and the `unifiedRoleAssignment` entity types with the sharing annotation. - -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -**In Microsoft.DirectoryServices.csdl**: - -This workload **only** defines the `unifiedRoleDefinition` and the `unifiedRoleAssignment` entity types without any properties, but with the sharing annotation. - -```xml - - - - - -``` diff --git a/graph/type-namespaces.md b/graph/type-namespaces.md deleted file mode 100644 index 27736f34..00000000 --- a/graph/type-namespaces.md +++ /dev/null @@ -1,185 +0,0 @@ -# Type namespaces - -Types should be declared in an appropriate namespace. As with traditional compiled libraries, putting types in namespaces creates a better developer experience. - -> Warning
    -> Type namespaces are not 1:1 with URL segmentation. For guidance on URL segmentation, see [[Singletons]]. - -## Namespace usage - -Namespaces are used both at runtime and in the developer experience. - -### Runtime - -At runtime, namespaces appear when using type cast segments and in `@odata.type` annotations. - -Type cast segments allow the caller to filter a collection to a given subtype. This usage is rare, but does happen in Microsoft Graph, e.g.: - -```http -GET https://graph.microsoft.com/v1.0/directory/deletedItems/microsoft.graph.user -``` - -We are working to make type cast segment unqualified when the type name is unambiguous. The effect of this change will be that the namespace in the type cast segment is only required when multiple types with that name exist. - -```http -GET https://graph.microsoft.com/v1.0/directory/deletedItems/user -``` - -Type cast segments also appear in `@odata.type` annotations. `@odata.type` annotations are only included if: - -- The type is ambiguous, such as when the type appears in a collection of a supertype. -- Type information was explicitly requested by asking for `odata.metadata=full`. - -### Developer experience - -Namespaces also affect the developer experience: - -| Area | Effect | -| -------------- | ------------------------------------------------- | -| SDKs | Types are generated in the appropriate namespace. | -| Docs | API reference is organized by namespace. | -| Changelog | Changes are categorized by namespace. | -| Graph Explorer | No effect. | - -## Namespace declaration - -The type namespace is declared in the `Namespace` attribute on the `Schema` that is uploaded to Microsoft Graph. - -```xml - -... - -``` - -### Historic behavior - -Until late 2018, Microsoft Graph merged all types into a single namespace, `microsoft.graph`, effectively ignoring the namespace specified in the schema. When the same type name appeared in multiple schemas, the types were merged. This provides teams with the ability to extend a type owned by a different team. - -There are several facets of the historic behavior that should be maintained: - -- Workloads should be able to explicitly declare types in the namespace `microsoft.graph`. -- Workloads should be able to extend types owned by other workloads. -- Types with the same name and namespace should be merged to allow extensibility. - -### Backwards compatibility - -It is a breaking change to change a type's namespace. Types that are in the namespace `microsoft.graph` in `v1.0` must stay in that namespace until Graph 2.0. A workload may technically start introducing types in their custom namespace even if the bulk of their types are in `microsoft.graph`. It is up to API reviewers to determine on a case-by-case basis whether this makes sense. - -## Namespace heuristic - -Microsoft Graph uses a specific heuristic to determine whether the namespace specified on the schema should be exposed publicly. This has two advantages. - -1. No changes need to be made to existing schemas or workloads to ensure that they keep working without introducing a breaking change. -2. The heuristic enforces that all namespaces must begin with `microsoft.graph.`. - -The heuristic is simple: if the namespace string starts with `microsoft.graph.` (case-insensitive, but must include the trailing `.`), the namespace will be publicly exposed. If the namespace does not begin with that exact string, all types in the schema will be coerced into the `microsoft.graph` namespace. - -| Schema namespace (case-insensitive match) | Public namespace | -| -------------------------------------------- | -------------------------------------------- | -| `MyNamespace` | `microsoft.graph` | -| `Microsoft.Graph` | `microsoft.graph` | -| `Microsoft.Graph.MyNamespace` | `microsoft.graph.myNamespace` | -| `Microsoft.Graph.MyNamespace.MySubNamespace` | `microsoft.graph.myNamespace.mySubNamespace` | - -### Namespace coercion - -Namespaces will be coerced in two ways: - -1. Casing will be coerced to `lowerCamelCase`. -2. Namespace length will be coerced to 4 segments. - -| Schema namespace (case-insensitive match) | Coerced namespace | -| ----------------------------------------- | ---------------------------------- | -| `microsoft.graph.MyNamespace` | `microsoft.graph.myNamespace` | -| `microsoft.graph.myNamespace.sub1.sub2` | `microsoft.graph.myNamespace.sub1` | - -## Namespace ownership - -Graph does not enforce namespace ownership. However, namespaces do have key contacts that should be consulted when modifying types in that namespace. - -Types must exist within a namespace, and workloads must explicitly state which namespace a type exists in. This does not mean that a workload must have their own namespace. A workload could continue to use their internal namespace or explicitly state that the types are in `microsoft.graph`. - -| Namespace | Owner | -| --------------------------- | ---------------------------------------------------------------- | -| microsoft.graph.callRecords | [IC3 Records Distribution team](mailto:ic3recdist@microsoft.com) | - -## Extensibility and cross-referencing - -To extend a type in a different schema, a workload must declare that schema and the type in it. This is conceptually similar to .NET partial types. - -To reference a type in a different schema, simply refer to that type by fully qualified name (namespace + type name). - -OData fully supports cross-referencing and will not fail even if a cycle happens between schemas. That said, normal cyclical constraints apply - a cycle in inheritance will break things. - -## Managing multiple schemas - -Workloads must define schemas in their csdl using the Edmx format. [Microsoft.IC3.DataPlatform](https://microsoftgraph.visualstudio.com/onboarding/_git/AGS-OnboardingAutomationPipeline?path=%2FMicrosoft.IC3.DataPlatform.csdl&version=GBschemas%2Fprd%2Fbeta&_a=contents) is an example of a workload that exposes multiple namespaces. - -::: tip -As with schemas that exist in the `microsoft.graph` namespace, defining an `entity` type is optional, AGS will transform your schema to make all entity types derive from `microsoft.graph.entity`. -::: - -::: warning -Do not deviate from the general structure in the example below. GMM expects the XML structure (including xml namespace declarations) to match the example below. -::: - -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -### Public `$metadata` - -The publicly hosted `$metadata` endpoint will have multiple schemas - one per coerced namespace. The primary entity container will exist in the schema with the `microsoft.graph` namespace. - -```xml - - - - - - - - - - - - - - - - ... - - - - - ... - - - - -``` diff --git a/graph/webhooks.md b/graph/webhooks.md deleted file mode 100644 index fb635426..00000000 --- a/graph/webhooks.md +++ /dev/null @@ -1,362 +0,0 @@ -# Adding support for Change Notifications - -Microsoft Graph Change Notifications enables application to subscribe to resource changes happening across Microsoft Graph without having to continuously poll the API for changes. Change notifications can be delivered to the subscriber either via webhooks (the subscriber exposes a notification URL) or via Azure Events Hub (first party only at the moment). For an overview of the general concept, please [refer to the public documentation](https://docs.microsoft.com/en-us/graph/webhooks). - -## Why should you add change notifications support for your entities? - -There are different scenarios where customers need to trigger custom logic execution based on changes in Microsoft Graph. These scenarios range from updating UI to reflect changes in the Graph to scenarios that need to analyze data (e.g. DLP, compliance, line of business apps...). -Today, if your API surface does not support change notifications, the only avenue for customers to implement such scenarios is by **continuously query your API surface**. This increases the complexity and cost of such solutions for customers or makes implementing certain scenarios impossible at scale. More importantly, it greatly increases COGS for the Microsoft Graph as well as your API. - -## Architectural overview - -![webhooks architecture](WebHooks-architecture.svg) - -The diagram above documents a simplified overview of the service components involved in the change notifications infrastructure: - -- **Customer web service**: this service is owned by he customer (or subscriber). It creates and renews subscriptions by calling Microsoft Graph and receives change notifications. -- **Aggregator Service**: this service is the publicly exposed service that customer applications query. (aka Microsoft Graph, AGS) -- **Subscription Service**: this middleware service _acts as a workload_ from the AGS' perspective and _looks like the AGS_ from a workload's perspective. This service is owned by the Microsoft Graph Change Tracking Team and performs the following tasks: - - Receives and responds to any incoming `/subscriptions` request from the AGS. - - Maintains a store of subscriptions. - - Validates the `notificationUrl` during subscription creation. - - Validates the `notificationUrl` performance on regular basis. - - Coordinates subscriptions CRUD operations with workloads by calling workloads APIs. -- **Publisher Service**: this service receives any notification emitted by workloads, batches multiple notifications to the same notification URL, and delivers notifications. This service also handles retrying delivery when the subscriber's notification URl is slow and/or not available. This service is owned by the Microsoft Graph Change Tracking Team. -- **Workload Service**: this service is the target workload's own API. It maintain it's local subscription store, implements an API for subscriptions CRUD operations and emits notifications. - -## How to add support for change notifications for your entity types - -### Update the public documentation - -1. Add your resource to [the table of permissions](https://docs.microsoft.com/en-us/graph/api/subscription-update?view=graph-rest-beta&tabs=http#permissions). This needs to be done for [Get](https://docs.microsoft.com/en-us/graph/api/subscription-get?view=graph-rest-beta&tabs=http#permissions), [Create](https://docs.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-beta&tabs=http#permissions), [Update](https://docs.microsoft.com/en-us/graph/api/subscription-update?view=graph-rest-beta&tabs=http#permissions) and [Delete](https://docs.microsoft.com/en-us/graph/api/subscription-delete?view=graph-rest-beta&tabs=http#permissions) (\*). -1. Add the maximum expiration time entry [in the table](https://docs.microsoft.com/en-us/graph/api/resources/subscription?view=graph-rest-beta#maximum-length-of-subscription-per-resource-type) (\*). -1. Add the example [resource paths](https://docs.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-beta&tabs=http#example) (\*). -1. Add the resource in the [overview page](https://docs.microsoft.com/en-us/graph/api/resources/webhooks?view=graph-rest-beta) as well as a [permission](https://docs.microsoft.com/en-us/graph/api/resources/webhooks?view=graph-rest-beta#permissions) (\*). -1. Add an entry into [supported resources](https://docs.microsoft.com/en-us/graph/webhooks#supported-resources) on the concept page (\*\*). -1. Add an entry in the [change log](https://docs.microsoft.com/en-us/graph/changelog), [guidance](../../Document/Guidelines/Changelog) -1. Add an entry in the [latency table](https://docs.microsoft.com/en-us/graph/webhooks#latency) (\*\*). - -> \*: These pages are available for beta and v1.0, make sure you update the beta pages during the public preview of change notifications support for your API. Make sure you update v1.0 pages when support ships for general availability. Updates for different versions can be done in different pull request. - -> \*\*: Make sure you include a (preview) next to your resource name during the public preview of change notifications support for your API. Make sure you remove the (preview) once the support for change notifications is generally available. - -### Provide required information to support - -In order to provide proper support for our customers, the support teams need information you need to provide them with. - -This information will be provided by starting a separate process owned by support called SPOT. To start the process, [create a new intake](https://microsoftspot.azurewebsites.net/Intake). A release manager will then contact you and guide you through the process of collecting and documenting the required information for support teams. - -When creating the SPOT intake, make sure you indicate you are adding change notifications support for your workload and set the following fields: - -- Disclosure level: No restrictions -- Release Type: Product/Service/Program -- Release Sub Type: Feature -- Responsible Org: Deployment Services -- Responsible Team: DS C+AI Team - -In relevant links, add the [change notifications support wiki](https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/313638/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks) `https://supportability.visualstudio.com/AzureAD/_wiki/wikis/AzureAD/313638/Microsoft-Graph-Notifications-and-Change-Tracking-using-webhooks` - -Some of the information that support will require to add it to their internal documentation include: - -- The ICM service and team for escalation -- The owning team (distribution list) - -### Update the schema metadata - -The subscription service relies on matching the subscription's resource property with Microsoft Graph schema metadata to understand which workload it should relay requests to. This process: - -1. Gets the entity that matches the resource property. -1. Get the `ags:AddressUrl` property of that entity from the schema. -1. Uses the Fully Qualified Domain Name or hostname of the URL to determine which adapter to load. -1. Uses the adapter to determine the full URL of the workload's subscriptions API and call it. - -You need to make sure you have: - -1. An entity onboarded to Microsoft Graph and defined in the metadata -1. This entity has an `ags:AddressUrl` property defined. -1. This entity you are the workload that owns the entity (i.e. no `ags:IsMaster` attribute or the attribute is set to `true`). - -> The entity referenced by a subscription may be hidden via `ags:IsHidden` if the entity is not a publicly available entity but needs to provide support for change notifications (first-party only scenarios). - -If your entity does not meet the requirements listed above, you need to update the schema definition of your entity, or add your entity if it is not defined all together. Please refer to the [guidance](../../../Rollout/Publish-schema/Test-using-VSTS-repo)Test-using-VSTS-repo). - -### Handle subscriptions CRUD operations - -Subscriptions created by applications are maintained in two separate services, you must provide an API that supports CRUD operations for subscriptions and implement an adapter in the Microsoft Graph subscription service that will relay calls to the former API as described below. - -#### The workload's subscription service - -This service is in charge of reflecting any operation on subscription relayed by the subscription service into the workloads data store. The API is usually collocated with the workload's API. The API you are building to implement subscriptions CRUD operations at a workload's level should be as close as possible to the [public-facing API](https://docs.microsoft.com/en-us/graph/api/resources/subscription?view=graph-rest-beta) to limit the need for payload transformations. You should also consider the following points: - -- Requests to GET existing subscriptions are not proxied to the workload, these are served by the Subscription Store service directly. -- When authorizing requests from the Subscription Store service, the actor of the PFT will be the subscription store service, not AGS. -- The Subscription Store service will use your service's DNS name as the resource URI when requesting tokens, so this needs to be registered on your first-party app. -- For POST operations, the Microsoft Graph Subscription Service will substitute the following properties that were provided by the client: - - `notificationUrl`: replaced by the URL of the Publisher Service that the workload should use to submit new notifications. - > This new URL is region specific allowing the workload to send notifications to the publisher in the corresponding region. Should the URL change for any reason, the Subscription Service will delete and re-create subscriptions for the workload by calling the workload's subscription service. - - `clientState`: replaced by a randomly generated one in order to protect the customer's service. - -> The subscription and publisher services will also replace the subscription id provided by the workload with a unified subscription id generated by the subscription service during the subscription creation. - -#### The Microsoft Graph Subscription Service - -This service is in charge of maintaining the integrity of subscriptions across internal services (Microsoft Graph and workloads) and coordinates calls to workloads stores to do so. For the subscription store to be aware of new resources that can be subscribed to and start relaying CRUD operations to the workload's, you need to implement an adapter in the **Subscription Service**: - -- You can see a sample of such implementation [on our repository](https://dev.azure.com/msazure/One/_git/AD-AGSChangeTracking/commit/39038e51f9f9c57481e21d32c7b3243920de9163). -- You must specify your workload's application id in the Subscription Publisher service's configuration for tokens to be accepted. -- When submitting the pull request: - 1. Share workload contacts: dev owner alias, dev group alias, and ICM service and team. - 1. Assign members of aadctdevs as reviewers. - -To get started with the repository: - -1. Fork the [AD-AGSChangeTracking](https://dev.azure.com/msazure/One/_git/AD-AGSChangeTracking) repository (you only need the master branch, More instructions on fork can be found [here](https://dev.azure.com/msazure/Forks)). -1. Clone your fork to your local machine. -1. Open a shell as admin and navigate to the newly cloned repository. -1. Run `init.cmd`. -1. Run `init.cmd -createshortcut`, this will create a shortcut on your desktop. -1. Open the shortcut as an administrator. -1. Navigate to `/src/dev/Notifications` (cd). -1. Run `vsmsbuild dirs.proj` to open the projects in Visual Studio. - -> This repository is built on top of coreXT practices, for more information on how to get started, please refer to the [documentation](https://microsoft.sharepoint.com/teams/corext/LivingDocs/Home.aspx) - -To get the tests running locally: - -1. Request the Graph Subscription team to grant you permissions to the `graphwebhooks-Test` Azure KeyVault. (see [How to get help](#how-to-get-help) to request it on Teams.) -1. Update the default processor architecture to x64: - - 1. In Visual Studio select `Test Settings`. - 1. Then select `Default Processor Architecture`. - 1. Then select `X64`. - - ![Image](WebHooks-vs.png) - -> 'build' must be run from the src/dev/notifications folder. Any test failures must be re-run in visual studio. A couple tests fail intermittently, running those multiple times should result in all passing. StyleCop failures must be addressed. - -> Visual studio 2019 is required. Open the project: src/dev/Notifications> vsmsbuild dirs.proj - -#### OData query parameters - -By default the subscription service will forward OData query parameters in the resource property. It is up to the workload to decide whether they want to support those or not. - -If the workload decides not to support OData query parameters, it should return a valid OData error that explicitly calls out the fact that OData query parameters are not supported for that resource. The subscription service will respond to the initial request with the provided error. - -If the workload decides to support OData query parameters, it should implement those: - -- **\$filter**: if the entity emitting the filter, a notification should be emitted, if not no notification should be emitted. e.g. for messages `$filter=IsRead eq 'true'` should only emit notifications when the `IsRead` property of the message is `true`. If no filter is provided, all notifications should be emitted given they match other conditions (changeType, ...). -- **\$select**: if the change is on a property included in the select statement, a notification should be emitted. If the change is not on a property included no notification should be emitted. If no select is provided, all notifications should be emitted given they match other conditions (changeType, ...). -- **\$top**: not supported in this context. -- **\$orderby**: not supported in this context. -- **\$expand**: not supported in this context. -- **\$count**: not supported in this context. -- **\$format**: not supported in this context. -- **\$search**: not supported in this context. -- **\$skip**: not supported in this context. - -### Send notifications to the publisher service - -You must send any notification to the publisher service's using the `notificationUrl` (POST) that was provided by the Microsoft Graph Subscription Service during the subscription creation. - -The notification URL will require an `Authorization` request header with the following value `Bearer ` where the bearer token is an app-only JWT obtained from EvoSTS for the Microsoft Graph Change Tracking App Id as audience (0bf30f3b-4a52-48df-9a82-234910c4a086). - -> Note: you need to app your app id in the publisher configuration as outlined in the Pull Request sample provided in [The Microsoft Graph Subscription Service](#the-microsoft-graph-subscription-service) section. - -Here is an example payload publishing one notification. - -```json -{ - "Value": [ - { - "@odata.type": "#Microsoft.OutlookServices.Notification", - "Id": null, - "SubscriptionExpirationDateTime": "2019-02-14T23:56:30.1307708Z", - "ChangeType": "created", - "subscriptionId": "MTE1MTVlYTktMjVkZS00MjY3LWI1YzYtMjg0NzliZmRhYWQ2", - "resource": "https://outlook.office365.com/api/beta/Users('userId@tenantId')/Events('')", - "clientState": "WebhooksSubscriptionState1", - "resourceData": { - "Id": "", - "@odata.etag": "", - "@odata.id": "https://outlook.office365.com/api/beta/Users('userId@tenantId')/Events('')", - "@odata.type": "#Microsoft.OutlookServices.Event", - "OtherResourceData": "some other resource data", - "OtherResourceData2": "some other resource data 2" - } - } - ] -} -``` - -| Property Path | Description | Required | Type | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------ | -| @odata.type | OData Type of the notification as represented on the workload | Yes | String | -| Id | Unique Id for notification | No | String | -| SubscriptionExpirationDateTime | Expiration Date and time of the subscription | Yes | String | -| ChangeType | Change type of the resource represented by the notification (created,updated,deleted) | Yes | String | -| SubscriptionId | Internal subscription Id as used by the workload, the Publisher Service will translate it to a public subscription Id for the subscriber | Yes | String | -| Resource | Resource URL on Microsoft Graph. Only exception for Exchange where the Publisher Service translates the value. | Yes | String | -| ClientState | Client state associated with the subscription | Yes | String | -| resourceData.id | Resource Id on Microsoft Graph. | Yes | String | -| resourceData.@odata.id | Resource URL on Microsoft Graph. Only exception for Exchange where the Publisher Service translates the value. | Yes | String | -| resourceData.@odata.etag | Opaque string representing the state of the resource as specified [here](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-odata/c4d715eb-10f6-47fa-9ccc-2ebf926558a6) | No | String | -| resourceData.@odata.type | Resource type as represented on Microsoft Graph. Only exception for Exchange. | Yes | String | -| resourceData.OtherResourceData | Additional properties to be attached. Should not contain customer data unless you are implementing rich notifications (see below) | No | Any | - -> The maximum number of notifications that can be sent at once is 100. Notifications for different subscriptions or tenants can be batched in the same call to the publisher service. - -### Life cycle notifications (preview)(optional) - -Life cycle notifications allow subscribers to get additional notifications whenever a change impacting the subscription itself occurs. When receiving a life cycle notification, subscribers can choose to try to auto-remedy the issue and/or let the end user know that an action is required from them. This feature is currently in public preview and you can learn more about it's usage in the [public documentation](https://docs.microsoft.com/en-us/graph/webhooks-lifecycle). - -You should always send life cycle notifications if you choose to implement them. Subscribers will receive them only if they provide an additional `lifecycleNotificationUrl` during subscription creation (POST). - -Life cycle notifications must be delivered to the `notificationUrl`. They do not include any resource data and must include a `changeType` property (string) for which the valid values are: - -- `missed`: notifications have not been sent to the subscriber because of a resource change, the subscriber should get the latest version of the resource from Microsoft Graph. -- `subscriptionRemoved`: the subscription has been deleted by the workload either because the resource itself has been deleted (e.g. a user left the company, the corresponding mailbox has been archived and the subscriptions to the mailbox have been deleted) or because of compliance issues (user's password reset, device out of compliance...). The subscriber should try to get the latest version of the resource from Microsoft Graph and if it still exists, subscribe again. The publisher service will delete the subscription from the subscription service automatically upon reception of such notification. -- `reauthorizationRequired`: the subscription notifications are still delivered to the publisher service by the workload, but the publisher service is holding them until the subscriber completes an [authorization challenge](https://docs.microsoft.com/en-us/graph/webhooks-with-resource-data#authorization-challenge-flow). If the subscriber fails to complete the challenge before the subscription's expiration time, notifications are dropped and the subscription is deleted. If the subscriber completes the authorization challenge in time, pending notifications are delivered. This is automatically generated by the publisher service to ensure that subscribers are still in compliance with access policies when delivering rich notifications (see below). - -> If the list of event types above does not cover your scenario, please let us know (see How to get help), the specification and public documentation allows for introducing new event types. The additional values cannot be any of `created`, `updated` or `deleted` as those values are reserved for classic notifications. - -> The `changeType` property will be renamed to `lifecycleEvent` by the publisher service before being delivered to subscribers so it matches the publicly documented schema. - -## Rich notifications (preview) - -Rich notifications, publicly known as [change notifications with resource data](https://docs.microsoft.com/en-us/graph/webhooks-with-resource-data), build on the foundation of change notifications. The major difference between rich notifications and basic notifications being that rich notifications include changed data with the notification delivered to the subscriber. This allows subscribers to understand change context without having to query Microsoft Graph saving them implementation efforts, latency and also saving us COGS. All implementation details provided by basic notifications still apply to rich notifications. - -### Handling rich notifications subscription CRUD requests - -Rich notification subscriptions are created the same way as basic notifications subscriptions. In addition to properties documented in the [Handle subscriptions CRUD operations](#handle-subscriptions-crud-operations) section, you'll receive the property `includeResourceData` set to `true` as an indicator that the subscriber wants to receive data with the notifications. If the resource does not support sending data, you must return an error. - -> Note: if you decide to encrypt the resource data before sending it to the publisher service (see below), you will also receive the following additional properties: `encryptionCertificate` which is a base64 encoded certificate public key, `encryptionCertificateId` which is a subscriber-defined identifier for the certificate and must be attached with the encrypted content. - -> Note: if you decide to encrypt the resource data before sending it to the publisher service (see below), you also need to handle `encryptionCertificate` and `encryptionCertificateId` updates on PATCH requests. - -### Encrypting changes notifications with resources payload - -The changed data attached to the notification is always encrypted when delivered to the subscriber. This ensures that under any circumstances the data will not be compromised or tampered with. - -Workloads have two main choices when it comes to encrypting the data: - -- You can delegate the encryption work to the publisher service, simply add any data property to the resourceData object. The publisher will take any property that is not whitelisted (id, odata.type, odata.id), remove them from the resourceData object and create the encrypted content with it. -- You can implement the encryption of the content yourself before submitting the notification to the publisher service. This is especially useful when you must respect data privacy and/or residency standards as it ensures that nobody else besides the workload and the subscriber can read the data. - -The choice should be carefully considered ahead of time as there's no migration plan in place today that supports changing "who encrypts the payload" once notifications start being sent. - -The encryption boundary is determined by the `RichNotificationsPayloadEncryptionEnabledForWorkloads` setting in the publisher service configuration. When your workload name is in the list of values, the publisher service will encrypt any data attached with the notification. Otherwise, the publisher service expects data to be encrypted prior to receiving the notification from the workload. - -#### Encrypting resource data on workload - -Should you choose to encrypt the payload, we provide a [utility class](https://dev.azure.com/msazure/One/_git/AD-AGSChangeTracking?path=%2Fsrc%2Fdev%2FNotifications%2FNotifications.Azure%2FEncryptor%2FEncryptor.cs&version=GBmaster) that helps you encrypt the content, the key and generate the signature. To get this library add the `Microsoft.Online.AggregatorService.Encryptor` from our [nuget feed](https://dev.azure.com/msazure/One/_packaging?_a=feed&feed=Official). - -> Note: the library is only available for dotnet standard today. - -The library will return 3 out of 5 required properties to add to the `encryptedContent` object: - -- `data`: the encrypted (using the single-use generated symmetric key) and base64 encoded representation of the JSON value you provided as input. -- `dataSignature`: the HMAC-SHA256 the digital signature of the `data` value. -- `dataKey`: the encrypted (using the subscriber's provided public key) and base64 encoded single-use generated symmetric key to be used to validate signature and decrypt the `data`. - -You are also required to add the following properties to the `encryptedContent` object before attaching it to the notification object: - -- `encryptionCertificateId`: the subscriber generated identified for the certificate provided during the subscription creation. -- `encryptionCertificateThumbprint`: the digital thumbprint of the certificate used for encryption of the single-use generated symmetric key. - -> The data, before it's encrypted, must be a JSON object with the same schema public schema of the entity the notification is emitted for. If any **\$select** query parameter is included with the resource property of the subscription object, only properties included in the select statement should be included. For more informations refer to [OData query parameters](#odata-query-parameters). - -Here is an example payload publishing one notification which contains encrypted content. - -```json -{ - "Value": [ - { - "subscriptionId": "c9101526-927a-4eec-95dd-f45683a97c6d", - "changeType": "Created", - "@odata.type": "#Microsoft.Graph.ChatMessage", - "clientState": "ClientSecret", - "subscriptionExpirationDateTime": "2018-05-20T18:23:45.9356913+00:00", - "resource": "teams('7434c182-5c07-418a-8116-5945d043b179')/channels('19:8da7bc05bc4e4e5c9b75b2cfe65124dc@thread.skype')/messages('1540929866182')/replies('1540929866243')", - "resourceData": { - "id": "1532471292816", - "@odata.type": "#Microsoft.Graph.ChatMessage", - "@odata.id": "teams('7434c182-5c07-418a-8116-5945d043b179')/channels('19:8da7bc05bc4e4e5c9b75b2cfe65124dc@thread.skype')/messages('1540929866182')/replies('1540929866243')" - }, - "EncryptedContent": { - "data": "lSI1f79", - "dataSignature": "", - "dataKey": "Xm6Ec/6v", - "encryptionCertificateId": "c4f2788d-914a-4494-903a-57522477a9b0", - "encryptionCertificateThumbprint": "78B664778B44CAECDFB11270E5549C1AD34AA0D9" - } - } - ] -} -``` - -### Sending validation tokens - -Because subscribers do not need anymore to query Microsoft Graph back upon receiving a rich notifications, and because they are exposing a publicly available URL, it is even more important for them to be able to validate the origin of notifications. It allows subscribers to avoid executing code on requests coming from potentially rogue actors. When delivering rich notifications the publisher subscriber will include an array of validation tokens on the notifications collection object. These tokens are JWTs valid for the subscribing application. **No action is required from the workload, they are added automatically.** - -## Additional information - -### Throttling behavior for subscriptions requests - -As requests for subscriptions are transiting through the subscription service before coming to the workload, this might bypass any throttling configuration you might have setup at the AGS level to enforce for the rest of your API surface. - -The subscription service defines throttling rules at the AGS level that will be enforced, which means the subscription service itself will not make more requests than it has defined at the AGS level to the workload. The latest values can be viewed [here](https://microsoftgraph.visualstudio.com/onboarding/_git/AGS-OnboardingAutomationPipeline?path=%2Fgraphags_Public.xml&version=GBThrottlingService&_a=contents) (search for throttle.aad.subscriptionservice) : - -- POST, PUT, DELETE, PATCH - per appid, 2000 requests in 20 sec period. -- POST, PUT, DELETE, PATCH - per tenant per appid, 1000 requests in a 20 sec period. -- All other HTTP methods - per appid, 10000 requests in a 20 sec period. -- All other HTTP methods - per tenant, per appid, 5000 requests in a 20 sec period. - -### End to end testing - -#### Local development flow - -If you are building the subscriptions CRUD API in your workload and you want to validate that it respects the expected contract on your local dev machine, you can leverage unit tests to perform such validation. : - -- Navigate to the `src/dev/Notifications/ExtendedTests/ExtendedTests.EndToEnd/E2ETests.cs` class on your local copy of the service repository. -- Update the `ExchangeEndpointUrl` property value by the local URL of the API you are developing ([ref](https://dev.azure.com/msazure/One/_git/AD-AGSChangeTracking?path=%2Fsrc%2Fdev%2FNotifications%2FExtendedTests%2FExtendedTests.EndToEnd%2FE2ETests.cs&version=GBmaster&line=127&lineEnd=127&lineStartColumn=30&lineEndColumn=49&lineStyle=plain)). -- Run the tests. - -> These unit tests are designed to work with mocked APIs and will send invalid access tokens, you should disable authorization for the subscriptions part of your API. - -#### Service End-to-end testing - -You can test the end to end scenario (creating a subscription, generating notifications, receiving notifications) in PPE. Once your code changes have been deployed on the workloads side and your pull request to the subscription/publisher service has been merged, you can request the on call engineer to deploy your build of the subscription/publisher service to PPE. The contact of the on call engineer can be found on [our ICM board](https://portal.microsofticm.com/imp/v3/oncall/current?serviceId=20330&teamIds=36748&scheduleType=timeline&shiftType=current&viewType=1). - -You can also leverage [our public sample](https://github.com/microsoftgraph/aspnetcore-webhooks-sample) as a dummy service that will create and receive notifications. - -### First-Party only notifications - -Microsoft Graph change notifications provides support for notifications available only for first party applications. -The following table lists the resources available for subscription: - -| Resource | Description | -| ---------------------- | ---------------------------------------------------------------------------- | -| AllTenantAssignedPlans | Triggers a notification whenever plans assignment are updated on any tenant. | -| AllTenantTenantDelete | Triggers a notification whenever a tenant gets deleted. | -| AllTenantUserDelete | Triggers a notification whenever a user gets deleted on any tenant. | - -#### How to create a first party subscription - -1. [Create a subscription](https://docs.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-1.0&tabs=http) on the `users` resource with your first party application. -1. [Email the team](mailto:AADCTdevs@microsoft.com?body=Please%20fill%20in%20the%20following%20details:%0A-%20Resource:%20%0A-%20Environment:%20%0A-%20App%20ID:%0A-%20Subscription%20ID:%20&subject=Request%20for%20first%20party%20subscription) providing the following details: - - Resource (from the table above) - - Environment (PROD/public, PPE/public, Mooncake, US gov...) - - App Id - - Subscription ID - -> **Note:** Due to the high volume of notifications you might receive, we recommend you leverage [Azure Event Hubs delivery](https://github.com/microsoftgraph/microsoft-graph-docs/pull/7462) instead of webhook delivery for the notifications. - -## How to get help - -Should you need any help during your design and implementation, there are a couple of ways you can reach out: - -- [Stackoverflow.com](https://stackoverflow.com): for any question that does not contain confidential, internal or customer related information. Example: questions about ASP.NET core MVC, questions about the OData libraries, etc. -- [Internal StackOverflow](https://stackoverflow.microsoft.com): for any question that might contain confidential or internal information. Example: how do I configure the routing in AGS to do... -- [Teams: Microsoft Graph > Webhooks](https://teams.microsoft.com/l/channel/19%3a4139ee2d50a54a14a75365d505d27364%40thread.skype/Webhooks?groupId=6d279915-6f5d-452c-895b-7f4f82038843&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47): for any question that contains details really specific to your implementation, escalation of questions left unanswered on one of the StackOverflow platforms, ... - -Ask: please refrain from asking questions directly to the engineering team via either emails, Teams chat etc... This does not scale and it doesn't capture the question (and answer) for other people that might have the same question as you. You should always try to ask a question on stack overflow first before reaching out on the Teams channel. diff --git a/graph/~$delling with Facets Pattern.md b/graph/~$delling with Facets Pattern.md deleted file mode 100644 index 817e118875072ab276dab2a4f0a5422a6d399ded..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmd<{&q+^I2*^*#qz&SxMF@GpEk|@@o?c=Q1#S;cD2(aQiwVOjq5tUFiUZVjXt? diff --git a/graph/~WRL0034.tmp b/graph/~WRL0034.tmp deleted file mode 100644 index aba38c33..00000000 --- a/graph/~WRL0034.tmp +++ /dev/null @@ -1,134 +0,0 @@ -# Facets Pattern - -Microsoft Graph API Design Pattern - -  - -*A frequent pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with common properties and one facet property (of complex type) per variant.* --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - -## Context - -Let’s assume you need to model an API to manage documents, pictures, files of different formats which are organized in different folder hierarchies across multiple local and shared drives. - -where employees can create groups and become owners of the -group by default. At the same time to support business processes some -groups may be created automatically by daemon applications using a -service principal account. In this case the service principle will -become the group owner. People and service principles have some common -and some unique properties such as both have unique identifiers and -credentials, but users will have additional properties such as email and -manager for example. Conversely a service principle won’t have a manager -assigned but may have an associated application identifier and a -description. - -Additional requirement for the API model is to be flexible and -accommodate future needs like a device may become the owner of a group. - -## Problem - -How to model group owners to allow a heterogeneous collection of users -and principles so that API design can reflect common and relevant -properties for each subset of owners and accommodate new owner types in -future? - -A more general problem is how to model a collection of heterogeneous elements that have a set of common properties and behaviors, and some unique properties for each variant. Furthermore, it should be straightforward to add new variants to the API as needed in the future. - -## Solution --------- - -OData allows us to design collections of entities (entity sets, multi -valued navigation properties) with values of different types using -**type hierarchy**, where there is one abstract base type with a few -common properties and one sub-type for each variant of the entity. In -the current version of Microsoft Graph there are many collections of -items that represent slightly different things, variants of one concept. - -## Issues and Considerations -------------------------- - -When introducing a new subtype, you need to ensure that the new subtype -doesn't change the semantic of the type hierarchy with it's implicit -constraints. - -There are a **few potential risks** for client applications when new -sub-types are introduced: - -- De-serialization code might break because of missing -properties in returned collection items. Even though property X was -mandatory on all subtypes previously returned, the new subtype might not -have this property and the client code needs to deal with that. - -- Client libraries for strongly typed language might ignore some -of the values in the @odata.type property without further configuration -and need to be configured to be able to pick the right (client) type to -deserialize into. - -To minimize impact on clients type hierarchy can be refined by -annotating the collections with OData derived type constraints (see -validation vocabulary). This annotation restricts the values to certain -sub-trees of an inheritance hierarchy. It makes it very explicit that -the collection only contains elements of some of the subtypes and helps -to not return objects of a type that is semantically not suitable. In -addition, you can follow some of the mitigation techniques such as: - -- Avoid overgeneralized base types - -- Think about roll-out sequence - - Consider that Microsoft Graph does not return objects from a workload -that has a type that is not configured in current metadata. To avoid -inconsistencies, follow a two-step process: - - Introduce the entity type to the Graph metadata but don’t -return objects of the type in any of the heterogeneous collections. - - Enable your workload to return objects of the new type as items -of collection. - - -- Allow time for testing - - Inform the clients about the change and allow them to test the -changes in beta. Time is required to implement the code necessary to -deal with the new entity type, both in terms of de-serialization as well -as integrating it into the rest of the application. - -- Communicate the change in semantics - - - It is necessary for the client developers to incorporate the new -semantic into their application/service, even if the change is perceived -to be small. This requires early communication and clear documentation -of what the new type represents and why/how it is considered a subtype -of the original abstract type of the collection. - -## When to Use this Pattern ------------------------- - -The Type hierarchy pattern is well familiar to OOP developers and well -suited for strongly typed client programming languages. - -There are related patterns to consider such as -[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) -and [Flat bag of -properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). - -## Example -------- - -GET -[https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners](https://graph.microsoft.com/v1.0/groups/02bd9fd6-8f93-4758-87c3-1fb73740a315/owners)  -returns a collection where each element can be a user or a service -principal, and has an additional property @odata.type to show subtype -for each variant: -``` -{ -    "@odata.context": -"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", -    "value": [ -        { -            "@**odata.type**": "\#**microsoft.graph.user**", -            "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", -            "userPrincipalName": "MeganB@M365x214355.onmicrosoft.com" -            // ... -        } -    ] -} -``` From 48172da30cce2a6745b5492d7d6e5947da118c7c Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 28 Jan 2022 17:37:09 -0500 Subject: [PATCH 259/729] updated pattern links --- graph/GuidelinesGraph.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e13305d8..133192f3 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -418,8 +418,10 @@ Recommended API Design patterns: | Pattern | Description | Reference | |-------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| -| Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Modeling with Subtypes](./Modelling%20with%20Subtypes%20Pattern.md) | -| Facets | The ability to model parent-child relationships using Facet pattern. | [Modeling with Facets](./Modelling%20with%20Subtypes%20Pattern.md) | +| Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Modeling with Subtypes](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | +| Facets | The ability to model parent-child relationships using Facet pattern. | [Modeling with Facets](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | +| Dictionary | The ability for client to provide an unknown quantity of data elements of the same type. | [Dictionary](./patterns/dictionary.md) | + | From 55cf829e5d9dd8dd973949fd2dd7edd301fd5bf0 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 28 Jan 2022 17:40:04 -0500 Subject: [PATCH 260/729] spelling --- graph/GuidelinesGraph.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 133192f3..a0ea07df 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -418,9 +418,9 @@ Recommended API Design patterns: | Pattern | Description | Reference | |-------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| -| Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Modeling with Subtypes](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | -| Facets | The ability to model parent-child relationships using Facet pattern. | [Modeling with Facets](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | -| Dictionary | The ability for client to provide an unknown quantity of data elements of the same type. | [Dictionary](./patterns/dictionary.md) | +| Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Subtypes](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | +| Facets | The ability to model parent-child relationships using Facet pattern. | [Facets](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | +| Dictionary | The ability for clients to provide an unknown quantity of data elements of the same type. | [Dictionary](./patterns/dictionary.md) | | From f6bef85a39d34172e9c930b966846356f8371b17 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 31 Jan 2022 20:05:29 -0800 Subject: [PATCH 261/729] Fix breaking changes link --- azure/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/README.md b/azure/README.md index 1926753b..ba88203c 100644 --- a/azure/README.md +++ b/azure/README.md @@ -5,7 +5,7 @@ Designing powerful APIs with strong defaults, consistent behavior across related * [Considerations for Service Design](ConsiderationsForServiceDesign.md) * [REST API Guidelines](Guidelines.md) * [OpenAPI Style Guidelines](https://github.com/Azure/azure-api-style-guide/blob/main/README.md) -* [Breaking Changes](https://aka.ms/azapi/breakingchanges) +* [Breaking Changes](http://aka.ms/AzBreakingChangesPolicy/) You can reach out to use via [email](mailto://azureapirbcore@microsoft.com) or in our [Teams](https://teams.microsoft.com/l/team/19%3a3ebb18fded0e47938f998e196a52952f%40thread.tacv2/conversations?groupId=1a10b50c-e870-4fe0-8483-bf5542a8d2d8&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) channel. From 6cdd694c201f5fc117d04dfa6328d5b6f3e22a1b Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 1 Feb 2022 18:42:18 -0500 Subject: [PATCH 262/729] Updated timeline to reflect adding error guidance --- azure/ConsiderationsForServiceDesign.md | 1 + azure/Guidelines.md | 1 + 2 files changed, 2 insertions(+) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index f3044d22..f4afe0b2 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -4,6 +4,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2022-Feb-01 | Updated error guideance | | 2021-Sep-11 | Add long-running operations guidance | | 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | diff --git a/azure/Guidelines.md b/azure/Guidelines.md index f0b29e18..db8b07f3 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -3,6 +3,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2022-Feb-01 | Updated error guidance | | 2021-Sep-11 | Add long-running operations guidance | | 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | | 2020-Jul-31 | Added service advice for initial versions | From 43d8cd632de3fef954772e597569eb2081f63be4 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 1 Feb 2022 18:45:54 -0500 Subject: [PATCH 263/729] Updated timeline to reflect adding error guidance --- azure/ConsiderationsForServiceDesign.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index f4afe0b2..2cf54311 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -4,7 +4,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | -| 2022-Feb-01 | Updated error guideance | +| 2022-Feb-01 | Updated error guidance | | 2021-Sep-11 | Add long-running operations guidance | | 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | From 867594280e72104fb886749d421f4fcbfbb77a19 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 1 Feb 2022 19:15:47 -0500 Subject: [PATCH 264/729] renamed patterns --- graph/patterns/{Modelling with Facets Pattern.md => facets.md} | 0 .../patterns/{Modelling with Subtypes Pattern.md => subtypes.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename graph/patterns/{Modelling with Facets Pattern.md => facets.md} (100%) rename graph/patterns/{Modelling with Subtypes Pattern.md => subtypes.md} (100%) diff --git a/graph/patterns/Modelling with Facets Pattern.md b/graph/patterns/facets.md similarity index 100% rename from graph/patterns/Modelling with Facets Pattern.md rename to graph/patterns/facets.md diff --git a/graph/patterns/Modelling with Subtypes Pattern.md b/graph/patterns/subtypes.md similarity index 100% rename from graph/patterns/Modelling with Subtypes Pattern.md rename to graph/patterns/subtypes.md From 3866290d35035d41fe4c7e8a7cef01e2f846baa1 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 1 Feb 2022 19:20:58 -0500 Subject: [PATCH 265/729] Update graph/GuidelinesGraph.md Co-authored-by: Gareth Jones --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index a0ea07df..3582332a 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -418,7 +418,7 @@ Recommended API Design patterns: | Pattern | Description | Reference | |-------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| -| Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Subtypes](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | +| Type Hierarchy | The ability to model is-a relationships using subtypes. | [Subtypes](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | | Facets | The ability to model parent-child relationships using Facet pattern. | [Facets](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | | Dictionary | The ability for clients to provide an unknown quantity of data elements of the same type. | [Dictionary](./patterns/dictionary.md) | From 798272c0ccfd84157d610a46511c36837ffc857a Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Thu, 3 Feb 2022 18:48:48 -0500 Subject: [PATCH 266/729] Update graph/GuidelinesGraph.md Co-authored-by: Dan Kershaw [MSFT] --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 3582332a..bb90e7fa 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -70,7 +70,7 @@ If not following these advices, you MUST disclose your reasons during the Graph ## Design Approach -The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote API-first design approach where you begin your product design by focusing on how information will be exchanged and represented and creating an interface contract for your API which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface from your implementation and is essential for agility, predictability, and reuse of your APIs. Established interface contract allows developers to use your API while internal teams are still working on implementation, API specifications enable designing of user experience and test cases in parallel. Starting with user-facing contracts also promotes a good understanding of system interactions, your modeling domain, and understanding of how the service will evolve. Microsoft Graph supports resource and query-based API styles which follow HTTP, REST, and JSON standards, where API contract is described using ODATA conventions and schema definition (see Documentation · OData - the Best Way to REST). +The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote API-first design approach where you begin your product design by focusing on how information will be exchanged and represented and creating an interface contract for your API which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface from your implementation and is essential for agility, predictability, and reuse of your APIs. Established interface contract allows developers to use your API while internal teams are still working on implementation; API specifications enable designing of user experience and test cases in parallel. Starting with user-facing contracts also promotes a good understanding of system interactions, your modeling domain, and understanding of how the service will evolve. Microsoft Graph supports resource and query-based API styles which follow HTTP, REST, and JSON standards, where API contract is described using ODATA conventions and schema definition (see Documentation · OData - the Best Way to REST). [Documentation · OData - the Best Way to REST](https://www.odata.org/documentation/)). In general API design includes the following steps: From 70b236f0bc930fe1b9bf88fad424f5821366d2c0 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 9 Feb 2022 11:47:04 -0500 Subject: [PATCH 267/729] added scenarios bullet --- graph/GuidelinesGraph.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index bb90e7fa..70776629 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -75,6 +75,8 @@ The design of your API is arguably the most important investment you will make. In general API design includes the following steps: +- Outline the key current and future scenarios for API consumers + - Define your domain model - Derive and name your API resources From fb3438ebe8fbf3eb3be6b15e5ea75e5cb9cc06f3 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 14 Feb 2022 17:05:18 -0500 Subject: [PATCH 268/729] added a sce --- graph/GuidelinesGraph.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 70776629..e79d180e 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -105,7 +105,7 @@ Below is a short summary of the most often used conventions. | Requirements | Example | |---------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| :no_entry: **MUST NOT** use redundant words in names. |- **Right:** /places/{id}/**type** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeType* and /phones/{id}/**phoneNumber** | +| :no_entry: **MUST NOT** use redundant words in names. |- **Right:** /places/{id}/**displayName** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeName* and /phones/{id}/**phoneNumber** | | :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | | :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | | :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong** addresses | @@ -143,7 +143,7 @@ segments: - **category** segment is a logical grouping of APIs into top-level categories; -- **pathSegment** is the last navigation segment which can address an entity, +- **pathSegment** is one or many navigation segments which can address an entity, collection of entities, property or operation available for an entity - **query string** must follow the OData standard for query representations @@ -151,7 +151,7 @@ segments: While HTTP defines no constraints on how different resources are related together, it does encourage the use of URL path segment hierarchies to convey -relationships. In Microsoft Graph lifetime relationships between resources are +relationships. In Microsoft Graph relationships between resources are supported by the OData concepts of singletons, entitySets, entities, complex types and navigation properties. @@ -182,7 +182,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | | :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| -| :heavy_check_mark: **MUST** support server-side pagination for collections | +| :heavy_check_mark: **MUST** support server-side pagination for large collections | | :ballot_box_with_check: **SHOULD** support pagination $top, $skip and $count for collections | The query options part of an OData URL can be quite long, potentially exceeding @@ -204,8 +204,8 @@ Limitations of \$query requests made to Microsoft Graph: ### Resource Modeling Patterns -You can model complex resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity type declares a key property to uniquely identify its objects and Complex Type does not. In Microsoft Graph this key property has "id" as a prescribed name. -Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources. Complex types are better suited to represent composite properties of API entities. +You can model complex resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity type declares a key property to uniquely identify its objects and Complex Type does not. In Microsoft Graph this key property is called "id" for server-created key values. If there is a natural name for the key property then the workload can use that. +Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources, such as individually addressable resources within a collection. Complex types are better suited to represent composite properties of API entities. ```XML @@ -383,10 +383,10 @@ breaking change. - Addition of a required request header - Addition of a EnumType members for non-evolvable enumerations - Addition of a Nullable="false" properties to existing types -- Addition of a Nullable="false" parameters to existing actions and functions +- Addition of a parameter not marked as Nullable to existing actions or functions +- Addition of a parameter not marked as Optional to an existing function - Changes to top-level error codes - Introduction of server-side pagination to existing collections -- Changes to the default order of collection elements - Significant changes to the performance of APIs such as increased latency, rate limits or concurrency. @@ -403,7 +403,7 @@ Microsoft Graph provides two public endpoints to support API lifecycle: 1. API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in general availability (GA) status. 2. API sets on the beta endpoint (https://graph.microsoft.com/beta) are in beta or private preview status. -Microsoft Graph APIs in the GA version guarantee API stability and consistency for its clients. If your API requires a breaking change in GA, then you MUST create new element versions and support deprecated elements for a minimum of 36 months. +Microsoft Graph APIs in the GA version guarantee API stability and consistency for its clients. If your API requires a breaking change in GA, then you MUST create new element versions and support deprecated elements for a minimum of 36 months or 24 months with demonstrated non-usage. On the beta endpoint breaking changes and deprecation of APIs are allowed with consideration of dependencies and customer impact. It is best practice to test new element versions on the beta endpoint at first then promote API changes to the GA endpoint. Detailed requirements for versioning and deprecation are described in the [Deprecation guidelines](./deprecation.md). From fdef09f376420e41984e500d7894ac534fc4a435 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 14 Feb 2022 17:14:05 -0500 Subject: [PATCH 269/729] Versioning clarificattion --- graph/GuidelinesGraph.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e79d180e..86d9ff2d 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -182,7 +182,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | | :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| -| :heavy_check_mark: **MUST** support server-side pagination for large collections | +| :heavy_check_mark: **MUST** support server-side pagination for collections | | :ballot_box_with_check: **SHOULD** support pagination $top, $skip and $count for collections | The query options part of an OData URL can be quite long, potentially exceeding @@ -393,10 +393,9 @@ breaking change. ### Versioning and Deprecation As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If that is not possible then you must version elements of your APIs. -Microsoft Graph allows versioning of elements including entities and properties. The versioning process goes along with deprecation and as soon as you introduce a new element update the previous version needs to follow the deprecation process. +Microsoft Graph allows versioning of elements including entities and properties. Versioning involves adding a new, uniquely named version of the element and marking the old version as deprecated. -You must create a new version of your element for any breaking change and name it uniquely. -In some cases, the API will have evolved such that there is a new, natural unique name. In other cases, the original name may still be the most descriptive for the evolved element. In the latter case, the suffix _v2 must be added to the original name to make it unique. +In some cases, there will be a natural new name for the element. In other cases, where the original name is still the most descriptive, the suffix _v2 can be added to the original name to make it unique. The original element is then marked as deprecated using annotations. Microsoft Graph provides two public endpoints to support API lifecycle: From 2698c9fd790441a1d3908fb05653c9adf730ce7c Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Mon, 14 Feb 2022 17:29:08 -0500 Subject: [PATCH 270/729] Update graph/GuidelinesGraph.md Co-authored-by: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 86d9ff2d..699497c3 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -184,7 +184,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m | :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| | :heavy_check_mark: **MUST** support server-side pagination for collections | | :ballot_box_with_check: **SHOULD** support pagination $top, $skip and $count for collections | - +| :ballot_box_with_check: **SHOULD** sorting with \$orderby both ascending and descending on properties of the entities The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or processing the request. One way to avoid this is to use the POST verb instead of From 1b080f0b9b9561914c85cbd0e259bf0d6409d1f5 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 14 Feb 2022 17:34:27 -0500 Subject: [PATCH 271/729] pagination --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 86d9ff2d..240faeb2 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -182,7 +182,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | | :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| -| :heavy_check_mark: **MUST** support server-side pagination for collections | +| :heavy_check_mark: **MUST** support [server-side pagination](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_ServerDrivenPaging) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL) for collections | | :ballot_box_with_check: **SHOULD** support pagination $top, $skip and $count for collections | The query options part of an OData URL can be quite long, potentially exceeding From bbcdbf59437818f9c42e19abdef3b511b0654d8a Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 14 Feb 2022 19:19:17 -0500 Subject: [PATCH 272/729] updated beta to v1.0 --- graph/GuidelinesGraph.md | 9 +++++---- graph/patterns/facets.md | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index b054747a..6e5a1ffb 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -184,7 +184,8 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m | :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| | :heavy_check_mark: **MUST** support [server-side pagination](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_ServerDrivenPaging) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL) for collections | | :ballot_box_with_check: **SHOULD** support pagination $top, $skip and $count for collections | -| :ballot_box_with_check: **SHOULD** sorting with \$orderby both ascending and descending on properties of the entities +| :ballot_box_with_check: **SHOULD** sorting with \$orderby both ascending and descending on properties of the entities | + The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or processing the request. One way to avoid this is to use the POST verb instead of @@ -205,7 +206,7 @@ Limitations of \$query requests made to Microsoft Graph: ### Resource Modeling Patterns You can model complex resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity type declares a key property to uniquely identify its objects and Complex Type does not. In Microsoft Graph this key property is called "id" for server-created key values. If there is a natural name for the key property then the workload can use that. -Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources, such as individually addressable resources within a collection. Complex types are better suited to represent composite properties of API entities. +Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources, such as individually addressable items within a collection. Complex types are better suited to represent composite properties of API entities. ```XML @@ -383,8 +384,8 @@ breaking change. - Addition of a required request header - Addition of a EnumType members for non-evolvable enumerations - Addition of a Nullable="false" properties to existing types -- Addition of a parameter not marked as Nullable to existing actions or functions -- Addition of a parameter not marked as Optional to an existing function +- Addition of a parameter not marked as [Nullable](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Nullable) to existing actions or functions +- Addition of a parameter not marked as [Optional](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md#OptionalParameter) to an existing function - Changes to top-level error codes - Introduction of server-side pagination to existing collections - Significant changes to the performance of APIs such as increased latency, rate limits or concurrency. diff --git a/graph/patterns/facets.md b/graph/patterns/facets.md index 30a3569b..2ed85ccd 100644 --- a/graph/patterns/facets.md +++ b/graph/patterns/facets.md @@ -67,7 +67,7 @@ The driveItem resource represents a file, folder,image or other item stored in a API request to get all items from a personal OneDrive will return a heterogenous collection with different facets populated. In the example below there is a folder, a file and an image in the collection. The image entity has two facets populated: file and image. ``` -https://graph.microsoft.com/beta/me/drive/root/children +https://graph.microsoft.com/v1.0/me/drive/root/children Response shortened for readability: From 6c7a27604d9b67fa1799381852b0611aa0f18a32 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 18 Feb 2022 11:13:30 -0500 Subject: [PATCH 273/729] deprecation dates fix --- graph/GuidelinesGraph.md | 6 +++--- graph/deprecation.md | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 6e5a1ffb..928ac3df 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -205,8 +205,8 @@ Limitations of \$query requests made to Microsoft Graph: ### Resource Modeling Patterns -You can model complex resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity type declares a key property to uniquely identify its objects and Complex Type does not. In Microsoft Graph this key property is called "id" for server-created key values. If there is a natural name for the key property then the workload can use that. -Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources, such as individually addressable items within a collection. Complex types are better suited to represent composite properties of API entities. +You can model structured resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity type declares a key property to uniquely identify its objects and Complex Type does not. In Microsoft Graph this key property is called "id" for server-created key values. If there is a natural name for the key property then the workload can use that. +Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources, such as individually addressable items within a collection, for more information refer to [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. ```XML @@ -384,7 +384,7 @@ breaking change. - Addition of a required request header - Addition of a EnumType members for non-evolvable enumerations - Addition of a Nullable="false" properties to existing types -- Addition of a parameter not marked as [Nullable](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Nullable) to existing actions or functions +- Addition of a parameter not marked as [Nullable](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Nullable) to existing actions - Addition of a parameter not marked as [Optional](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md#OptionalParameter) to an existing function - Changes to top-level error codes - Introduction of server-side pagination to existing collections diff --git a/graph/deprecation.md b/graph/deprecation.md index 9c488446..cc818b9b 100644 --- a/graph/deprecation.md +++ b/graph/deprecation.md @@ -21,11 +21,11 @@ to annotate any usages of that type. - - + + - - + + @@ -35,13 +35,13 @@ to annotate any usages of that type. When the request URL contains a reference to a deprecated model element, the gateway will add a [Deprecation header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the -date the element was marked as deprecated) and a Sunset header (with the date 2 +date the element was marked as deprecated) and a Sunset header (with the date of 2 years beyond the Deprecation date) to the response. **Deprecation header example:** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Deprecation: Thursday, 30 June 2022 11:59:59 GMT -Sunset: Wed, 30 Mar 2022 23:59:59 GMT -Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2020-06-30_state ; rel="deprecation"; type="text/html"; title="state" +Deprecation: Wed, 30 Mar 2022 11:59:59 GMT +Sunset: Thursday, 30 June 2024 23:59:59 GMT +Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_state ; rel="deprecation"; type="text/html"; title="state" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From acda7e03ded96d48a5b7c2f452a94fc44f804e56 Mon Sep 17 00:00:00 2001 From: Darrel Date: Fri, 18 Feb 2022 12:58:57 -0500 Subject: [PATCH 274/729] Update graph/patterns/dictionary.md --- graph/patterns/dictionary.md | 1 - 1 file changed, 1 deletion(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 8d9b14e1..f23dc167 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -45,7 +45,6 @@ Before using a dictionary type in your API definition make sure your scenario fi ### Alternatives to consider - [Open extensions](https://docs.microsoft.com/en-us/graph/extensibility-open-users) when you want to provide clients the ability to extend Microsoft Graph. -- [Open types](https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/use-open-types-in-odata-v4) when your data is not a collection in nature. - [Complex types](https://docs.microsoft.com/en-us/odata/webapi/complextypewithnavigationproperty) when the set of data values are known. ## Examples From 929e223da5cf2ef51125d1b37d739d31b3edf0b0 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 18 Feb 2022 14:59:40 -0500 Subject: [PATCH 275/729] added JSON batch reference --- graph/GuidelinesGraph.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 928ac3df..b009703b 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -193,15 +193,7 @@ GET with $query segment, and pass the query options part of the URL in the reque in the chapter [OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). -Limitations of \$query requests made to Microsoft Graph: - -- Microsoft Graph only supports having all the query options completely in the - request body or completely in the request url. Graph doesn't support query - options present in both places. - -- The parameters in \$query segment should not span multiple workloads. Support for - \$query segment right now is limited to properties belonging to the same workload. - +Another way is to use JSON batch as described in the [Microsoft Graph Documentation] (https://docs.microsoft.com/en-us/graph/json-batching#bypassing-url-length-limitations-with-batching). ### Resource Modeling Patterns From 0c94b2b17d52a7bfabd00f7fd6cc70b7ef63d841 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 18 Feb 2022 15:10:43 -0500 Subject: [PATCH 276/729] updated POST recommendations Co-authored-by: Darrel --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index b009703b..f2e82386 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -268,7 +268,7 @@ APIs SHOULD use resource-based designs with standard HTTP methods rather than op | Microsoft Graph rules for modeling behavior | |-----------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections | +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections. This approach requires the server to produce system generated identities. | | :heavy_check_mark: **MUST** use PATCH to edit updatable resources | | :heavy_check_mark: **MUST** use DELETE to delete deletable resources | | :heavy_check_mark: **MUST** use GET for listing and reading resources. | From 04ab617f7cdb65832aaf3ebe983c6d00618541bd Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 18 Feb 2022 15:13:19 -0500 Subject: [PATCH 277/729] replaced action with operation Co-authored-by: Darrel --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index f2e82386..8418f6fb 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -273,7 +273,7 @@ APIs SHOULD use resource-based designs with standard HTTP methods rather than op | :heavy_check_mark: **MUST** use DELETE to delete deletable resources | | :heavy_check_mark: **MUST** use GET for listing and reading resources. | | :warning: **SHOULD NOT** use PUT for updating resources. | -| :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical action. | +| :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical operation. | Bound operations must have a binding parameter matching the type of the bound resource. From 4c7f0040f4f113c566ff56a26a2c804fd48540d4 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 18 Feb 2022 15:59:48 -0500 Subject: [PATCH 278/729] Update graph/GuidelinesGraph.md Co-authored-by: Dan Kershaw [MSFT] --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 8418f6fb..e78475cd 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -361,7 +361,7 @@ breaking change. - Addition of properties that are nullable or have a default value -- Addition of a member to an evolvable enumeration +- Addition of a member, after the sentinel member, to an evolvable enumeration - Removal, rename, or change to the type of an annotation - Changes to the order of properties - Changes to the length or format of opaque strings, such as resource IDs From e4568683428015b61b49058726c9890e1a4751e2 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 18 Feb 2022 16:01:13 -0500 Subject: [PATCH 279/729] error code fixes --- graph/GuidelinesGraph.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 8418f6fb..edecee4e 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -216,8 +216,8 @@ Since objects of complex types on Graph don’t have unique identifiers, they ar ``` -| Microsoft Graph rules for modeling complex resources | | -|---------------------------------------|------------------------------------------------------------| +| Microsoft Graph rules for modeling complex resources | +|----------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** use String type for id | | :heavy_check_mark: **MUST** use a primary key composed of a single property | | :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | @@ -278,7 +278,6 @@ APIs SHOULD use resource-based designs with standard HTTP methods rather than op Bound operations must have a binding parameter matching the type of the bound resource. In addition both actions and functions support overloading, meaning an API definition may contain multiple actions or functions with the same name. -Microsoft Graph supports the use of optional parameters. You can use the optional parameter annotation instead of creating function or action overloads. For a complete list of standard HTTP operations you can refer to the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). @@ -306,7 +305,7 @@ and consistency by using recommended Graph error model and the Graph Utilities l The top-level error code must be aligned with HTTP response status codes according to [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). The following examples demonstrate error modeling for common use cases: -- **Simple error**: A workload wants to report an error with top-level details +- **Simple error**: An API wants to report an error with top-level details only. Then the error object contains the top-level error code, message and target (optional). @@ -341,9 +340,9 @@ The following examples demonstrate error modeling for common use cases: | Microsoft Graph enforces the following error rules | |-----------------------------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | -| :heavy_check_mark: **MUST** return a 403 Forbidden error when insufficient scopes are present in the auth token. | -| :heavy_check_mark: **MUST** return a 429 Too many requests error when the caller has exceeded throttling limits. | -| :ballot_box_with_check: **SHOULD** return a 404 Not found error if a 403 would result in information disclosure. | +| :heavy_check_mark: **MUST** return a 403 Forbidden error when the application or signed-in user have insufficient permissions are present in the auth token. | +| :heavy_check_mark: **MUST** return a 429 Too Many Requests error when client exceeded throttling limits and 503 Service Unavailable when service overloaded but client is within throttling limits.| +| :ballot_box_with_check: **SHOULD** return a 404 Not Found error if a 403 would result in information disclosure. | For a complete mapping of error codes to HTTP statuses you can refer to the [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). From 14f54bcf7bbda9993840dae53e2e55dbcede8bb8 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 18 Feb 2022 16:17:19 -0500 Subject: [PATCH 280/729] Update graph/patterns/dictionary.md Co-authored-by: Darrel --- graph/patterns/dictionary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index f23dc167..b279f231 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -42,7 +42,7 @@ Before using a dictionary type in your API definition make sure your scenario fi - The value types MUST be a primitive type or is a **ComplexType**. Mixed primitive types are not allowed. - The client MUST define the keys of this type. As opposed to the service defining them in advance. -### Alternatives to consider +### Alternatives - [Open extensions](https://docs.microsoft.com/en-us/graph/extensibility-open-users) when you want to provide clients the ability to extend Microsoft Graph. - [Complex types](https://docs.microsoft.com/en-us/odata/webapi/complextypewithnavigationproperty) when the set of data values are known. From cd8a2b1ca7c8dd7aa79e02cdf42f8f64cd54871b Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 18 Feb 2022 16:32:24 -0500 Subject: [PATCH 281/729] minor fixes --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index dcd89267..bc4a7006 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -276,7 +276,7 @@ APIs SHOULD use resource-based designs with standard HTTP methods rather than op | :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical operation. | -Bound operations must have a binding parameter matching the type of the bound resource. +Operation resources must have a binding parameter matching the type of the bound resource. In addition both actions and functions support overloading, meaning an API definition may contain multiple actions or functions with the same name. For a complete list of standard HTTP operations you can refer to the [Microsoft From e70a9f6bbfa63f46e87d99ed08ffc0f50f129c14 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 18 Feb 2022 17:08:10 -0500 Subject: [PATCH 282/729] Delete SummaryOfGraphDeltas.md --- graph/SummaryOfGraphDeltas.md | 216 ---------------------------------- 1 file changed, 216 deletions(-) delete mode 100644 graph/SummaryOfGraphDeltas.md diff --git a/graph/SummaryOfGraphDeltas.md b/graph/SummaryOfGraphDeltas.md deleted file mode 100644 index 32c27230..00000000 --- a/graph/SummaryOfGraphDeltas.md +++ /dev/null @@ -1,216 +0,0 @@ -# Graph API Design API Patterns - -## Introduction - -The Graph REST API guidelines are an extension of the [Microsoft REST API guidelines](../guidelines). Readers of this document are assumed to be also reading the [Microsoft REST API guidelines](../guidelines) and be familiar with them. Graph guidance is a superset of the Microsoft API guidelines and services should follow them *except* where this document outlines specific differences or exceptions to those guidelines. This document does contain additional Graph-specific guidance and additional details. - -The following table of contents links back to the primary guidelines where there are no differences in Graph guidelines. Where differences exist, the section heading is **bold**. - -## 2. Table of contents - - -- [Microsoft REST API Guidelines Working Group](../Guidelines.md#microsoft-rest-api-guidelines-working-group) -- [1. Abstract](../Guidelines.md#1-abstract) -- [2. Table of contents](../Guidelines.md#2-table-of-contents) -- [3. Introduction](../Guidelines.md#3-introduction) - - [3.1. Recommended reading](../Guidelines.md#31-recommended-reading) -- [4. Interpreting the guidelines](../Guidelines.md#4-interpreting-the-guidelines) - - [4.1. Application of the guidelines](../Guidelines.md#41-application-of-the-guidelines) - - [4.2. Guidelines for existing services and versioning of services](../Guidelines.md#42-guidelines-for-existing-services-and-versioning-of-services) - - [4.3. Requirements language](../Guidelines.md#43-requirements-language) - - [4.4. License](../Guidelines.md#44-license) -- [5. Taxonomy](../Guidelines.md#5-taxonomy) - - [5.1. Errors](../Guidelines.md#51-errors) - - [5.2. Faults](../Guidelines.md#52-faults) - - [5.3. Latency](../Guidelines.md#53-latency) - - [5.4. Time to complete](../Guidelines.md#54-time-to-complete) - - [5.5. Long running API faults](../Guidelines.md#55-long-running-api-faults) -- [6. Client guidance](../Guidelines.md#6-client-guidance) - - [6.1. Ignore rule](../Guidelines.md#61-ignore-rule) - - [6.2. Variable order rule](../Guidelines.md#62-variable-order-rule) - - [6.3. Silent fail rule](../Guidelines.md#63-silent-fail-rule) -- [7. Consistency fundamentals](../Guidelines.md#7-consistency-fundamentals) - - [7.1. URL structure](../Guidelines.md#71-url-structure) - - [7.2. URL length](../Guidelines.md#72-url-length) - - [7.3. Canonical identifier](../Guidelines.md#73-canonical-identifier) - - [7.4. Supported methods](../Guidelines.md#74-supported-methods) - - [7.4.1. POST](../Guidelines.md#741-post) - - [7.4.2. PATCH](../Guidelines.md#742-patch) - - [7.4.3. Creating resources via PATCH (UPSERT semantics)](../Guidelines.md#743-creating-resources-via-patch-upsert-semantics) - - [7.4.4. Options and link headers](../Guidelines.md#744-options-and-link-headers) - - [7.5. Standard request headers](../Guidelines.md#75-standard-request-headers) - - [7.6. Standard response headers](../Guidelines.md#76-standard-response-headers) - - [7.7. Custom headers](../Guidelines.md#77-custom-headers) - - [7.8. Specifying headers as query parameters](../Guidelines.md#78-specifying-headers-as-query-parameters) - - [7.9. PII parameters](../Guidelines.md#79-pii-parameters) - - [7.10. Response formats](../Guidelines.md#710-response-formats) - - [7.10.1. Clients-specified response format](../Guidelines.md#7101-clients-specified-response-format) - - [7.10.2. Error condition responses](../Guidelines.md#7102-error-condition-responses) - - [7.11. HTTP Status Codes](../Guidelines.md#711-http-status-codes) - - [7.12. Client library optional](../Guidelines.md#712-client-library-optional) -- [8. CORS](../Guidelines.md#8-cors) - - [8.1. Client guidance](../Guidelines.md#81-client-guidance) - - [8.1.1. Avoiding preflight](../Guidelines.md#811-avoiding-preflight) - - [8.2. Service guidance](../Guidelines.md#82-service-guidance) -- [9. Collections](../Guidelines.md#9-collections) - - [9.1. Item keys](../Guidelines.md#91-item-keys) - - [9.2. Serialization](../Guidelines.md#92-serialization) - - [9.3. Collection URL patterns](../Guidelines.md#93-collection-url-patterns) - - [9.3.1. Nested collections and properties](../Guidelines.md#931-nested-collections-and-properties) - - [9.4. Big collections](../Guidelines.md#94-big-collections) - - [9.5. Changing collections](../Guidelines.md#95-changing-collections) - - [9.6. Sorting collections](../Guidelines.md#96-sorting-collections) - - [9.6.1. Interpreting a sorting expression](../Guidelines.md#961-interpreting-a-sorting-expression) - - [9.7. Filtering](../Guidelines.md#97-filtering) - - [9.7.1. Filter operations](../Guidelines.md#971-filter-operations) - - [9.7.2. Operator examples](../Guidelines.md#972-operator-examples) - - [9.7.3. Operator precedence](../Guidelines.md#973-operator-precedence) - - [9.8. Pagination](../Guidelines.md#98-pagination) - - [9.8.1. Server-driven paging](../Guidelines.md#981-server-driven-paging) - - [9.8.2. Client-driven paging](../Guidelines.md#982-client-driven-paging) - - [9.8.3. Additional considerations](../Guidelines.md#983-additional-considerations) - - [9.9. Compound collection operations](../Guidelines.md#99-compound-collection-operations) -- [**9a. Resource Design**](#9a-resource-design) - - [**9a.1. Noun Resources**](#9a1-noun-resources) - - [**9a.2. Verb Resources**](#9a2-verb-resources) - - [**9a.3. Resource Modeling**](#9a3-resource-modeling) -- [10. Delta queries](#10-delta-queries) - - [10.1. Delta links](../Guidelines.md#101-delta-links) - - [10.2. Entity representation](../Guidelines.md#102-entity-representation) - - [10.3. Obtaining a delta link](../Guidelines.md#103-obtaining-a-delta-link) - - [10.4. Contents of a delta link response](../Guidelines.md#104-contents-of-a-delta-link-response) - - [10.5. Using a delta link](../Guidelines.md#105-using-a-delta-link) -- [11. JSON standardizations](../Guidelines.md#11-json-standardizations) - - [11.1. JSON formatting standardization for primitive types](../Guidelines.md#111-json-formatting-standardization-for-primitive-types) - - [11.2. Guidelines for dates and times](../Guidelines.md#112-guidelines-for-dates-and-times) - - [11.2.1. Producing dates](../Guidelines.md#1121-producing-dates) - - [11.2.2. Consuming dates](../Guidelines.md#1122-consuming-dates) - - [11.2.3. Compatibility](../Guidelines.md#1123-compatibility) - - [11.3. JSON serialization of dates and times](../Guidelines.md#113-json-serialization-of-dates-and-times) - - [11.3.1. The `DateLiteral` format](../Guidelines.md#1131-the-dateliteral-format) - - [11.3.2. Commentary on date formatting](../Guidelines.md#1132-commentary-on-date-formatting) - - [11.4. Durations](../Guidelines.md#114-durations) - - [11.5. Intervals](../Guidelines.md#115-intervals) - - [11.6. Repeating intervals](../Guidelines.md#116-repeating-intervals) - - [**11.7. Evolvable Enums**](#117-evolvable-enums) - - [**11.8. Dictionary Types**](#118-dictionary-types) - - [**11.9. Ommitted Properties**](#119-ommitted-properties) -- [12. Versioning](../Guidelines.md#12-versioning) - - [12.1. Versioning formats](../Guidelines.md#121-versioning-formats) - - [12.1.1. Group versioning](../Guidelines.md#1211-group-versioning) - - [12.2. When to version](../Guidelines.md#122-when-to-version) - - [12.3. Definition of a breaking change](../Guidelines.md#123-definition-of-a-breaking-change) -- [**13. Long running operations**](#13-long-running-operations) - - [**13.1. Resource based long running operations (RELO)**](../long-running-operations.md#131-resource-based-long-running-operations-relo) - - [**13.2. Stepwise long running operations**](../long-running-operations.md#132-stepwise-long-running-operations) - - [13.2.1. PUT](../Guidelines.md#1321-put) - - [13.2.2. POST](../Guidelines.md#1322-post) - - [13.2.3. POST, hybrid model](../Guidelines.md#1323-post-hybrid-model) - - [13.2.4. Operations resource](../Guidelines.md#1324-operations-resource) - - [13.2.5. Operation resource](../Guidelines.md#1325-operation-resource) - - [13.2.6. Operation tombstones](../Guidelines.md#1326-operation-tombstones) - - [13.2.7. The typical flow, polling](../Guidelines.md#1327-the-typical-flow-polling) - - [13.2.8. The typical flow, push notifications](../Guidelines.md#1328-the-typical-flow-push-notifications) - - [13.2.9. Retry-After](../Guidelines.md#1329-retry-after) - - [13.3. Retention policy for operation results](../Guidelines.md#133-retention-policy-for-operation-results) -- [14. Throttling, Quotas, and Limits](../Guidelines.md#14-throttling-quotas-and-limits) - - [14.1. Principles](../Guidelines.md#141-principles) - - [14.2. Return Codes (429 vs 503)](../Guidelines.md#142-return-codes-429-vs-503) - - [14.3. Retry-After and RateLimit Headers](../Guidelines.md#143-retry-after-and-ratelimit-headers) - - [14.4. Service Guidance](../Guidelines.md#144-service-guidance) - - [14.4.1. Responsiveness](../Guidelines.md#1441-responsiveness) - - [14.4.2. Rate Limits and Quotas](../Guidelines.md#1442-rate-limits-and-quotas) - - [14.4.3. Overloaded services](../Guidelines.md#1443-overloaded-services) - - [14.4.4. Example Response](../Guidelines.md#1444-example-response) - - [14.5. Caller Guidance](../Guidelines.md#145-caller-guidance) - - [14.6. Handling callers that ignore Retry-After headers](../Guidelines.md#146-handling-callers-that-ignore-retry-after-headers) -- [**15. Push notifications via webhooks**](#15-push-notifications-via-webhooks) - - [15.1. Scope](../Guidelines.md#151-scope) - - [15.2. Principles](../Guidelines.md#152-principles) - - [15.3. Types of subscriptions](../Guidelines.md#153-types-of-subscriptions) - - [15.4. Call sequences](../Guidelines.md#154-call-sequences) - - [15.5. Verifying subscriptions](../Guidelines.md#155-verifying-subscriptions) - - [15.6. Receiving notifications](../Guidelines.md#156-receiving-notifications) - - [15.6.1. Notification payload](../Guidelines.md#1561-notification-payload) - - [15.7. Managing subscriptions programmatically](../Guidelines.md#157-managing-subscriptions-programmatically) - - [15.7.1. Creating subscriptions](../Guidelines.md#1571-creating-subscriptions) - - [15.7.2. Updating subscriptions](../Guidelines.md#1572-updating-subscriptions) - - [15.7.3. Deleting subscriptions](../Guidelines.md#1573-deleting-subscriptions) - - [15.7.4. Enumerating subscriptions](../Guidelines.md#1574-enumerating-subscriptions) - - [15.8. Security](../Guidelines.md#158-security) -- [16. Unsupported requests](../Guidelines.md#16-unsupported-requests) - - [16.1. Essential guidance](../Guidelines.md#161-essential-guidance) - - [16.2. Feature allow list](../Guidelines.md#162-feature-allow-list) - - [16.2.1. Error response](../Guidelines.md#1621-error-response) -- [17. Naming guidelines](../Guidelines.md#17-naming-guidelines) - - [17.1. Approach](../Guidelines.md#171-approach) - - [17.2. Casing](../Guidelines.md#172-casing) - - [17.3. Names to avoid](../Guidelines.md#173-names-to-avoid) - - [17.4. Forming compound names](../Guidelines.md#174-forming-compound-names) - - [17.5. Identity properties](../Guidelines.md#175-identity-properties) - - [17.6. Date and time properties](../Guidelines.md#176-date-and-time-properties) - - [17.7. Name properties](../Guidelines.md#177-name-properties) - - [17.8. Collections and counts](../Guidelines.md#178-collections-and-counts) - - [17.9. Common property names](../Guidelines.md#179-common-property-names) - - [**17.10. Type namespaces**](#1710-type-namespaces) -- [18. Appendix](../Guidelines.md#18-appendix) - - [18.1. Sequence diagram notes](../Guidelines.md#181-sequence-diagram-notes) - - [18.1.1. Push notifications, per user flow](../Guidelines.md#1811-push-notifications-per-user-flow) - - [18.1.2. Push notifications, firehose flow](../Guidelines.md#1812-push-notifications-firehose-flow) - - [**18.2. Additional resources**](#182-additional-resources) - - - -## Summaries of the deltas in the Microsoft Graph Design Guidelines - -### 9a. Resource Design - -#### 9a.1. Noun Resources - -While HTTP defines no constraints on how different resources are related together, it does encourage the use of URL path segment hierarchies to convey a relationship. In addition to the hierarchy of resources, there are also lifetime relationships between resources, the notions of [singletons, entitySets, entities, complex types and navigation properties](entity-complex) make it possible to define a set of lifetime relationships between resources. - -#### 9a.2. Verb Resources - -Noun-based resources are not always the best fit for meeting the requirements of a client. There are read-only and write scenarios where a resource can be used to represent some kind of data processing operation. The terms [function and action](Functions-and-actions) are used to identify read and write operation style resources, respectively. - -#### 9a.3. Resource Modeling - -There are a number of principles to be aware of when modeling resources for Microsoft Graph. [Modeling variants](modeling-variants) is important when resources have have a subset of common properties and behavior. - -### 10. Deltas - -The ability to track changes (pull) occuring in the data exposed by Microsoft Graph. This is addressed implementing [change tracking (aka delta query)](deltas). - -### 11.7. Evolvable Enums - -[Evolvable enums](evolvable-enums) enable the use of enumerations that can add new values over time without breaking clients applications. - -### 11.8. Dictionary Types - -For scenarios where there is a need to persist a variable number of properties, a [dictionary type](./dictionary/index.md) may be useful. - -### 11.9. Omitting Properties - -For scenarios where the server contains business logic that determines if a property value should be returned, or not, to the client, a returned representation can be annotated to indicate where properties are [omitted](ommitting-properties). - -### 13. Long running operations - -Long running operations are mostly unchanged. The most significant difference is that instead of using `Operation-Location` as the header to point to the Operation, the use of the standard `Location` header is recommended. In hybrid scenarios, a `Content-Location` header can be used to indicate the URL of the created resource and the 202 response can contain a payload. Details of the diffences are described in detail in the [Long Running Operations](long-running-operations) document. - -### 15. Push notifications via webHooks - -To determine how the Graph docs differ than the Microsoft REST API Guidelines. -The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph. This is addressed implementing [change notifications (aka webhooks)](webhooks). - -### 17.10. Type Namespaces - -Microsoft Graph model types can be declared within a [type namespaces](type-namespaces) to reduce the need to prefix types with a qualifier to ensure uniqueness. - -### 18.2. Additional resources - -The links below provide a set of rules to think through as you design your API. - -- [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/) -- [OData Guidelines](http://www.odata.org/documentation/) -- [Microsoft Graph Documentation](https://developer.microsoft.com/en-us/graph/docs/concepts/overview) -- [Microsoft Graph Explorer](https://aka.ms/ge) From b253c4b790c04ca31c4e5654d8c7cd7d21d38ba3 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Wed, 23 Feb 2022 11:58:02 -0800 Subject: [PATCH 283/729] Added pointer to Graph-specific guidelines. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1a0ca3ec..b62534f1 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ We further hope that these guidelines may encourage other organizations to creat ### Additional guidance for Azure service teams Azure service teams should reference the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. +### Additional guidance for Microsoft Graph service teams +Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. + [![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) ## Code of Conduct From 8465bf242ad1743beb49caa90a5a36c9190dc648 Mon Sep 17 00:00:00 2001 From: "Leibniz.Hu" Date: Thu, 24 Feb 2022 14:26:08 +0800 Subject: [PATCH 284/729] fix: Fix wrong position of period and comma (#291) --- Guidelines.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Guidelines.md b/Guidelines.md index dc194627..c9812c89 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -380,7 +380,7 @@ Because PUT is defined as a complete replacement of the content, it is dangerous Clients that do not understand (and hence ignore) properties on a resource are not likely to provide them on a PUT when trying to update a resource, hence such properties could be inadvertently removed. Services MAY optionally support PUT to update existing resources, but if they do they MUST use replacement semantics (that is, after the PUT, the resource's properties MUST match what was provided in the request, including deleting any server properties that were not provided). -Under UPSERT semantics, a PATCH call to a nonexistent resource is handled by the server as a "create," and a PATCH call to an existing resource is handled as an "update." To ensure that an update request is not treated as a create or vice versa, the client MAY specify precondition HTTP headers in the request. +Under UPSERT semantics, a PATCH call to a nonexistent resource is handled by the server as a "create", and a PATCH call to an existing resource is handled as an "update". To ensure that an update request is not treated as a create or vice versa, the client MAY specify precondition HTTP headers in the request. The service MUST NOT treat a PATCH request as an insert if it contains an If-Match header and MUST NOT treat a PATCH request as an update if it contains an If-None-Match header with a value of "*". If a service does not support UPSERT, then a PATCH call against a resource that does not exist MUST result in an HTTP "409 Conflict" error. @@ -501,14 +501,14 @@ However, it is very generic and does not require specific OData constructs. APIs SHOULD use this format even if they are not using other OData constructs. The error response MUST be a single JSON object. -This object MUST have a name/value pair named "error." The value MUST be a JSON object. +This object MUST have a name/value pair named "error". The value MUST be a JSON object. -This object MUST contain name/value pairs with the names "code" and "message," and it MAY contain name/value pairs with the names "target," "details" and "innererror." +This object MUST contain name/value pairs with the names "code" and "message", and it MAY contain name/value pairs with the names "target", "details" and "innererror." The value for the "code" name/value pair is a language-independent string. Its value is a service-defined error code that SHOULD be human-readable. This code serves as a more specific indicator of the error than the HTTP error code specified in the response. -Services SHOULD have a relatively small number (about 20) of possible values for "code," and all clients MUST be capable of handling all of them. +Services SHOULD have a relatively small number (about 20) of possible values for "code", and all clients MUST be capable of handling all of them. Most services will require a much larger number of more specific error codes, which are not interesting to all clients. These error codes SHOULD be exposed in the "innererror" name/value pair as described below. Introducing a new value for "code" that is visible to existing clients is a breaking change and requires a version increase. @@ -521,13 +521,13 @@ Services SHOULD NOT localize "message" for the end user, because doing so might The value for the "target" name/value pair is the target of the particular error (e.g., the name of the property in error). -The value for the "details" name/value pair MUST be an array of JSON objects that MUST contain name/value pairs for "code" and "message," and MAY contain a name/value pair for "target," as described above. +The value for the "details" name/value pair MUST be an array of JSON objects that MUST contain name/value pairs for "code" and "message", and MAY contain a name/value pair for "target", as described above. The objects in the "details" array usually represent distinct, related errors that occurred during the request. See example below. The value for the "innererror" name/value pair MUST be an object. The contents of this object are service-defined. -Services wanting to return more specific errors than the root-level code MUST do so by including a name/value pair for "code" and a nested "innererror." Each nested "innererror" object represents a higher level of detail than its parent. +Services wanting to return more specific errors than the root-level code MUST do so by including a name/value pair for "code" and a nested "innererror". Each nested "innererror" object represents a higher level of detail than its parent. When evaluating errors, clients MUST traverse through all of the nested "innererrors" and choose the deepest one that they understand. This scheme allows services to introduce new error codes anywhere in the hierarchy without breaking backwards compatibility, so long as old error codes still appear. The service MAY return different levels of depth and detail to different callers. @@ -591,7 +591,7 @@ Example of "innererror": } ``` -In this example, the most basic error code is "BadArgument," but for clients that are interested, there are more specific error codes in "innererror." +In this example, the most basic error code is "BadArgument", but for clients that are interested, there are more specific error codes in "innererror." The "PasswordReuseNotAllowed" code may have been added by the service at a later date, having previously only returned "PasswordDoesNotMeetPolicy." Existing clients do not break when the new error code is added, but new clients MAY take advantage of it. The "PasswordDoesNotMeetPolicy" error also includes additional name/value pairs that allow the client to determine the server's configuration, validate the user's input programmatically, or present the server's constraints to the user within the client's own localized messaging. @@ -655,7 +655,7 @@ The spirit behind CORS is to avoid preflight for any simple cross-domain request All other requests require preflight. A request is "simple" and avoids preflight if its method is GET, HEAD or POST, and if it doesn't contain any request headers besides Accept, Accept-Language and Content-Language. -For POST requests, the Content-Type header is also allowed, but only if its value is "application/x-www-form-urlencoded," "multipart/form-data" or "text/plain." +For POST requests, the Content-Type header is also allowed, but only if its value is "application/x-www-form-urlencoded", "multipart/form-data" or "text/plain." For any other headers or values, a preflight request will happen. ### 8.2. Service guidance @@ -680,7 +680,7 @@ Services used by interactive Web clients where performance is critical SHOULD av - Avoid requiring cookies. XmlHttpRequest will only send cookies on cross-domain requests if the "withCredentials" attribute is set; this also causes a preflight request. - Services that require cookie-based authentication MUST use a "dynamic canary" to secure all APIs that accept cookies. -- For POST calls, prefer simple Content-Types in the set of ("application/x-www-form-urlencoded," "multipart/form-data," "text/plain") where applicable. Any other Content-Type will induce a preflight request. +- For POST calls, prefer simple Content-Types in the set of ("application/x-www-form-urlencoded", "multipart/form-data", "text/plain") where applicable. Any other Content-Type will induce a preflight request. - Services MUST NOT contravene other API recommendations in the name of avoiding CORS preflight requests. In particular, in accordance with recommendations, most POST requests will actually require a preflight request due to the Content-Type. - If eliminating preflight is critical, then a service MAY support alternative mechanisms for data transfer, but the RECOMMENDED approach MUST also be supported. @@ -1205,7 +1205,7 @@ O | RealNumber | "OLE Date"; integral part is the number of days sin T | SignedInteger | "Ticks"; number of ticks (100-nanosecond intervals) since midnight January 1, 1601. *See note below.* | [MSDN][ticks-time] U | SignedInteger | "UNIX"; number of seconds since midnight, January 1, 1970. | [MSDN][unix-time] W | SignedInteger | "Windows"; number of milliseconds since midnight January 1, 1601. *See note below.* | [MSDN][windows-time] -X | RealNumber | "Excel"; as for `O` but the year 1900 is incorrectly treated as a leap year, and day 0 is "January 0 (zero)." | [Microsoft Support][excel-time] +X | RealNumber | "Excel"; as for `O` but the year 1900 is incorrectly treated as a leap year, and day 0 is "January 0 (zero)". | [Microsoft Support][excel-time] **Important note for `C` and `W` kinds:** The native CLR and Windows times are represented by 100-nanosecond "tick" values. To interoperate with ECMAScript clients that have limited precision, _these values MUST be converted to and from milliseconds_ when (de)serialized as a `DateLiteral`. @@ -1252,7 +1252,7 @@ For example, "P3Y6M4DT12H30M5S" represents a duration of "three years, six month - Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z" - Start and duration, such as "2007-03-01T13:00:00Z/P1Y2M10DT2H30M" - Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z" -- Duration only, such as "P1Y2M10DT2H30M," with additional context information +- Duration only, such as "P1Y2M10DT2H30M", with additional context information ### 11.6. Repeating intervals [Repeating Intervals][wikipedia-iso8601-repeatingintervals], as per [ISO 8601][wikipedia-iso8601-repeatingintervals], are: @@ -1260,7 +1260,7 @@ For example, "P3Y6M4DT12H30M5S" represents a duration of "three years, six month > Formed by adding "R[n]/" to the beginning of an interval expression, where R is used as the letter itself and [n] is replaced by the number of repetitions. Leaving out the value for [n] means an unbounded number of repetitions. -For example, to repeat the interval of "P1Y2M10DT2H30M" five times starting at "2008-03-01T13:00:00Z," use "R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M." +For example, to repeat the interval of "P1Y2M10DT2H30M" five times starting at "2008-03-01T13:00:00Z", use "R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M." ## 12. Versioning **All APIs compliant with the Microsoft REST API Guidelines MUST support explicit versioning.** It's critical that clients can count on services to be stable over time, and it's critical that services can add features and make changes. From 62f4de2905be75b9a0baaadd0efa0af5cd60cee4 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 1 Nov 2021 11:05:59 -0400 Subject: [PATCH 285/729] - adds pattern description for change notifications --- graph/change-tracking.md | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 graph/change-tracking.md diff --git a/graph/change-tracking.md b/graph/change-tracking.md new file mode 100644 index 00000000..0313ca1b --- /dev/null +++ b/graph/change-tracking.md @@ -0,0 +1,46 @@ +Change Tracking {align="center" style="text-align:center"} +============ + +Microsoft Graph API Design Pattern + +Change tracking enables users to keep a third party system in sync with changes in Microsoft Graph without having to continuously poll the API. + +Context +------- + +In the world of distributed systems and richness of devices, keeping data in sync across systems is paramount to delivering a great user experience. + +* * + +Problem: + +Allowing customers to keep data in sync on a third party system can be complex and lead to exposing internal service architecture/implementation details. Examples of such details that can be challenging to both sides implementers include: + +- Partitions management and requests affinity -> two external requests can fall on different replicas that don't have the same sync state. +- Entities updates tracking -> the way entities are sync internally and tracking mechanisms should not "leak" externally. (update id, update date, conflicts resolution...) +- Multiple sync destinations -> having multiple systems syncing to the same dataset should not impact behavior/performance of the system. + +* * + +Solution +-------- + +Change Tracking (aka delta) provides developers with a new endpoint they can use to sync their third party system, get a delta link with a watermark, go offline, and comeback with that delta link to get new changes since their last request. Delta guarantees integrity of data through the watermark, regardless of service partitions and other obscure aspects for clients. + +* * + +Issues and Considerations +------------------------- + +Implementer MUST implement a watermark storage system in case of active watermarks. + +When to Use this Pattern +------------------------ + +Use this pattern when you want to provide customers with the ability to sync the data to a third party system. +Avoid this pattern when customers want to get notified of changes, or for backup/export scenarios. + +Example +------- + +A good example of such scenario could be the typical contacts list stored in Microsoft 365 services and available in multiple client applications. Obviously having all these client applications continuously polling the service would be suboptimal and deliver a poor end user experience (sync across service partitions without sticky sessions etc...). Using change tracking clients are able to sync contacts, go offline, and get new changes when they come back online. \ No newline at end of file From 465a5467caf7d7cf20bb04b170f9df7f89be4bf3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 25 Feb 2022 15:42:03 -0500 Subject: [PATCH 286/729] - updates change tracking pattern to newer template Signed-off-by: Vincent Biret --- graph/change-tracking.md | 46 ---------------- graph/patterns/change-tracking.md | 90 +++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 46 deletions(-) delete mode 100644 graph/change-tracking.md create mode 100644 graph/patterns/change-tracking.md diff --git a/graph/change-tracking.md b/graph/change-tracking.md deleted file mode 100644 index 0313ca1b..00000000 --- a/graph/change-tracking.md +++ /dev/null @@ -1,46 +0,0 @@ -Change Tracking {align="center" style="text-align:center"} -============ - -Microsoft Graph API Design Pattern - -Change tracking enables users to keep a third party system in sync with changes in Microsoft Graph without having to continuously poll the API. - -Context -------- - -In the world of distributed systems and richness of devices, keeping data in sync across systems is paramount to delivering a great user experience. - -* * - -Problem: - -Allowing customers to keep data in sync on a third party system can be complex and lead to exposing internal service architecture/implementation details. Examples of such details that can be challenging to both sides implementers include: - -- Partitions management and requests affinity -> two external requests can fall on different replicas that don't have the same sync state. -- Entities updates tracking -> the way entities are sync internally and tracking mechanisms should not "leak" externally. (update id, update date, conflicts resolution...) -- Multiple sync destinations -> having multiple systems syncing to the same dataset should not impact behavior/performance of the system. - -* * - -Solution --------- - -Change Tracking (aka delta) provides developers with a new endpoint they can use to sync their third party system, get a delta link with a watermark, go offline, and comeback with that delta link to get new changes since their last request. Delta guarantees integrity of data through the watermark, regardless of service partitions and other obscure aspects for clients. - -* * - -Issues and Considerations -------------------------- - -Implementer MUST implement a watermark storage system in case of active watermarks. - -When to Use this Pattern ------------------------- - -Use this pattern when you want to provide customers with the ability to sync the data to a third party system. -Avoid this pattern when customers want to get notified of changes, or for backup/export scenarios. - -Example -------- - -A good example of such scenario could be the typical contacts list stored in Microsoft 365 services and available in multiple client applications. Obviously having all these client applications continuously polling the service would be suboptimal and deliver a poor end user experience (sync across service partitions without sticky sessions etc...). Using change tracking clients are able to sync contacts, go offline, and get new changes when they come back online. \ No newline at end of file diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md new file mode 100644 index 00000000..354ae082 --- /dev/null +++ b/graph/patterns/change-tracking.md @@ -0,0 +1,90 @@ +# Change Tracking + +Microsoft Graph API Design Pattern + +*The change tracking pattern provides the ability to keep a third party system in sync with changes in Microsoft Graph without having to continuously poll the API.* + +## Problem +--------- + +Third party systems require to keep data in sync with Microsoft Graph, and the API design should not allow for continuous polling as it'd be costly and wouldn't guarantee data integrity. + +## Solution +-------- + +API designers leverage the change tracking (delta) capability on the entity set and declare a delta function for API consumers to use when tracking changes happening in the system. + +This new endpoint can be uses to sync third party systems. This is achieved through returning a delta link with a watermark. Once the third party system comes back online, 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. + +## Issues and Considerations +------------------------- + +Implementer MUST implement a watermark storage system in case of active watermarks (cursor in data store, partition affinity, sync state in data store...). + +## When to Use this Pattern +------------------------ + +Before using the change tracking pattern in your API definition, make sure your scenario fits the following criteria: + +- API consumers want to sync the data. +- API consumers don't want to get notified of changes. (see change notifications pattern for this scenario). +- API consumers are not looking for a "one-time" export or back-up mechanism. + +### Alternatives + +- Change notifications pattern (TODO add link when described) +- Export pattern (TODO) +- Backup pattern (TODO) + +## Examples +------- + +### Getting changes for the users entity set + +```HTTP +GET https://graph.microsoft.com/v1.0/users/delta + +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users", + "@odata.deltaLink": "https://graph.microsoft.com/v1.0/users/delta?$deltatoken=mS5DuRZGjVL-abreviated", + "value": [ + { + "businessPhones": [ + "+1 309 555 0104" + ], + "displayName": "Grady Archie", + "givenName": "Grady", + "jobTitle": "Designer", + "mail": "GradyA@contoso.onmicrosoft.com", + "officeLocation": "19/2109", + "preferredLanguage": "en-US", + "surname": "Archie", + "userPrincipalName": "GradyA@contoso.onmicrosoft.com", + "id": "0baaae0f-b0b3-4645-867d-742d8fb669a2", + "manager@delta": [ + { + "@odata.type": "#microsoft.graph.user", + "id": "99789584-a1e1-4232-90e5-866170e3d4e7" + } + ] + }, + ] +} +``` + +### CSDL example + +```xml + + + + + + + + + + + + +``` From df13a2046880305b25cda1b4cdc98a3affe23a99 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 25 Feb 2022 15:42:30 -0500 Subject: [PATCH 287/729] - fixes code block to get syntax highlighting Signed-off-by: Vincent Biret --- graph/deprecation.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/graph/deprecation.md b/graph/deprecation.md index cc818b9b..b15efce7 100644 --- a/graph/deprecation.md +++ b/graph/deprecation.md @@ -16,22 +16,21 @@ to annotate any usages of that type. **Example of property annotation:** -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - - - - - - - - - - - - ... - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +```xml + + + + + + + + + + + + + +``` When the request URL contains a reference to a deprecated model element, the gateway will add a [Deprecation header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the From cbc471dc7ce83b809cf70fc0ce5d281b5929cbea Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 25 Feb 2022 16:16:05 -0500 Subject: [PATCH 288/729] - adds notes about the skip and next link Signed-off-by: Vincent Biret --- graph/patterns/change-tracking.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 354ae082..3ab7e58c 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -43,7 +43,9 @@ Before using the change tracking pattern in your API definition, make sure your ```HTTP GET https://graph.microsoft.com/v1.0/users/delta +``` +```json { "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users", "@odata.deltaLink": "https://graph.microsoft.com/v1.0/users/delta?$deltatoken=mS5DuRZGjVL-abreviated", @@ -72,6 +74,8 @@ GET https://graph.microsoft.com/v1.0/users/delta } ``` +> Note: the response contains an `@odata.nextLink` 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.skipLink` instance annotation the application can request right away to get the next page. + ### CSDL example ```xml From 2e6254798a45d66f21577b3a5c0efcff433b0013 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 7 Mar 2022 17:34:22 -0500 Subject: [PATCH 289/729] namespace pattern --- graph/patterns/namespace.md | 79 +++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 graph/patterns/namespace.md diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md new file mode 100644 index 00000000..0d4738d0 --- /dev/null +++ b/graph/patterns/namespace.md @@ -0,0 +1,79 @@ +# Namespace + +Microsoft Graph API Design Pattern + +### *The Namespace provides the ability to group resource definitions together into a logical set.* + +## Problem + +When building a complex offering API designers may need to model many different +resources and their relationships. For better user experience and +discoverability related API definitions need to be grouped together. Large API +surface may need to be partitioned to generate light-weight client SDKs and libraries. + +## Solution + +API designers can use the Namespace attribute of the CSDL schema to +declare a namespace and logically bundle related API entities in the Graph +metadata. +```XML + +... + +``` + +All resources declared within the namespace will be grouped together in the public +Graph metadata and may be used for partitioning. + +A public namespace must have "microsoft.graph.” prefix and be presented in camel +case, i.e microsoft.graph.myNamespace. + +When type casting is required in the API query, request or response a fully-qualified type name is represented as concatenation of a namespace and a type name. Consequently namespace should be aligned with API category +path segment. + +## When to Use this Pattern + +API resource grouping creates a user-friendly experience keeping all resources +for a specific use case close together. It also allows generating smaller SDKs +and libraries and limits length of IDE prompts such as Intellisense. + + +## Issues and Considerations + +1. Microsoft Graph consistency requirements discourage using the same type + names for different concepts even within different namespaces. Microsoft + Graph type names must be descriptive and almost always unique within the API + surface. + +2. Namespace must be consistent with API navigation path. + +3. When type name is ambiguous and requires a namespace qualifier changing namespace is a breaking change. + + +4. To extend a type in a different schema, a service must declare that schema + and the type in it. This is conceptually similar to .NET partial types. + +5. To reference a type in a different schema, simply refer to that type by + fully qualified name (namespace + type name). +6. Microsoft Graph has heuristic rules for declared namespaces: + + - All public namespaces must have a prefix ‘microsoft.graph’ + + - If a namespace does not begin with ‘microsoft.graph’ prefix, all types in + the schema will be coerced into the main ‘microsoft.graph’ namespace. + +## Example + +Namespace and type declarations: +```XML + +”\> +… + +… + + +``` + +Fully qualified type name: microsoft.graph.search.bookmark From 604ef4577f2bc9f9d520cbd040921704aa8f95cf Mon Sep 17 00:00:00 2001 From: Darrel Date: Wed, 9 Mar 2022 12:28:16 -0500 Subject: [PATCH 290/729] Update graph/patterns/change-tracking.md Co-authored-by: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> --- graph/patterns/change-tracking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 3ab7e58c..9d803a4f 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -14,7 +14,7 @@ Third party systems require to keep data in sync with Microsoft Graph, and the A API designers leverage the change tracking (delta) capability on the entity set and declare a delta function for API consumers to use when tracking changes happening in the system. -This new endpoint can be uses to sync third party systems. This is achieved through returning a delta link with a watermark. Once the third party system comes back online, 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. +This new endpoint can be used to sync third party systems. This is achieved through returning a delta link with a watermark. Once the third party system comes back online, 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. ## Issues and Considerations ------------------------- From a3b63db325e379a30a969238f934675f0b66c3e3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 9 Mar 2022 09:33:03 -0800 Subject: [PATCH 291/729] Apply suggestions from code review Co-authored-by: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> Co-authored-by: Darrel --- graph/patterns/change-tracking.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 9d803a4f..f4e1ca2f 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -2,17 +2,17 @@ Microsoft Graph API Design Pattern -*The change tracking pattern provides the ability to keep a third party system in sync with changes in Microsoft Graph without having to continuously poll the API.* +*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.* ## Problem --------- -Third party systems require to keep data in sync with Microsoft Graph, and the API design should not allow for continuous polling as it'd be costly and wouldn't guarantee data integrity. +API consumers require an efficient way to keep data in sync with Microsoft Graph, and the API design should not allow for continuous polling as it'd be costly and wouldn't guarantee data integrity. ## Solution -------- -API designers leverage the change tracking (delta) capability on the entity set and declare a delta function for API consumers to use when tracking changes happening in the system. +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. This new endpoint can be used to sync third party systems. This is achieved through returning a delta link with a watermark. Once the third party system comes back online, 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. From 6911f7d40ef94c9be96c9f42b2fdbf2e8354405b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 9 Mar 2022 09:46:05 -0800 Subject: [PATCH 292/729] Apply suggestions from code review Co-authored-by: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> --- graph/patterns/change-tracking.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index f4e1ca2f..cbbdd4c1 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -14,7 +14,7 @@ API consumers require an efficient way to keep data in sync with Microsoft Graph 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. -This new endpoint can be used to sync third party systems. This is achieved through returning a delta link with a watermark. Once the third party system comes back online, 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. +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. ## Issues and Considerations ------------------------- @@ -27,7 +27,7 @@ Implementer MUST implement a watermark storage system in case of active watermar Before using the change tracking pattern in your API definition, make sure your scenario fits the following criteria: - API consumers want to sync the data. -- API consumers don't want to get notified of changes. (see change notifications pattern for this scenario). +- API consumers don't want to be immediately notified of changes. (see change notifications pattern for this scenario). - API consumers are not looking for a "one-time" export or back-up mechanism. ### Alternatives @@ -74,7 +74,7 @@ GET https://graph.microsoft.com/v1.0/users/delta } ``` -> Note: the response contains an `@odata.nextLink` 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.skipLink` instance annotation the application can request right away to get the next page. +> 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. ### CSDL example From 01ac6cfa2e00db5f2d6b702bf83341693cbd30ac Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 9 Mar 2022 12:59:17 -0500 Subject: [PATCH 293/729] - adds a removed example --- graph/patterns/change-tracking.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index cbbdd4c1..06f9b0e7 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -70,6 +70,12 @@ GET https://graph.microsoft.com/v1.0/users/delta } ] }, + { + "id": "0bbbbb0f-b0b3-4645-867d-742d8fb669a2", + "@removed": { + "reason": "changed" + } + } ] } ``` From abbad8b9f41da02d225a9b51ac622057009c78bf Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 14 Mar 2022 09:50:37 -0400 Subject: [PATCH 294/729] Apply suggestions from code review --- graph/patterns/change-tracking.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 06f9b0e7..f18b80c1 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -7,7 +7,7 @@ Microsoft Graph API Design Pattern ## Problem --------- -API consumers require an efficient way to keep data in sync with Microsoft Graph, and the API design should not allow for continuous polling as it'd be costly and wouldn't guarantee data integrity. +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. ## Solution -------- @@ -16,10 +16,12 @@ API designers can enable the change tracking (delta) capability on entity collec 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. +> 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. + ## Issues and Considerations ------------------------- -Implementer MUST implement a watermark storage system in case of active watermarks (cursor in data store, partition affinity, sync state in data store...). +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...) ## When to Use this Pattern ------------------------ From 4ad0856625694cd4294c17f3cdc635706bfa0754 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 14 Mar 2022 09:52:34 -0400 Subject: [PATCH 295/729] - adds comment regarding filter/select/top/expand --- graph/patterns/change-tracking.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index f18b80c1..b6166f15 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -23,6 +23,8 @@ This new endpoint can be used to sync API consumers. This is achieved through re 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...) +> 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. + ## When to Use this Pattern ------------------------ From b136f087a6f88bbd1d2d6d79edcf2b49c2a7e1dc Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 14 Mar 2022 10:56:10 -0400 Subject: [PATCH 296/729] alignment to top-level categories --- graph/patterns/namespace.md | 91 ++++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 0d4738d0..79265fa4 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -9,34 +9,38 @@ Microsoft Graph API Design Pattern When building a complex offering API designers may need to model many different resources and their relationships. For better user experience and discoverability related API definitions need to be grouped together. Large API -surface may need to be partitioned to generate light-weight client SDKs and libraries. +surface may need to be partitioned to generate light-weight client SDKs and +libraries. ## Solution -API designers can use the Namespace attribute of the CSDL schema to -declare a namespace and logically bundle related API entities in the Graph -metadata. -```XML +API designers can use the Namespace attribute of the CSDL schema to declare a +namespace and logically bundle related API entities in the Graph metadata. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ XML ... -``` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -All resources declared within the namespace will be grouped together in the public -Graph metadata and may be used for partitioning. +All resources declared within the namespace will be grouped together in the +public Graph metadata and may be used for partitioning. A public namespace must have "microsoft.graph.” prefix and be presented in camel case, i.e microsoft.graph.myNamespace. -When type casting is required in the API query, request or response a fully-qualified type name is represented as concatenation of a namespace and a type name. Consequently namespace should be aligned with API category -path segment. +When type casting is required in the API query, request or response a fully +qualified type name is represented as concatenation of a namespace and a type +name. Consequently namespace should be aligned with API category path segment. ## When to Use this Pattern API resource grouping creates a user-friendly experience keeping all resources -for a specific use case close together. It also allows generating smaller SDKs +for a specific feature close together. It also allows generating smaller SDKs and libraries and limits length of IDE prompts such as Intellisense. +We recommend creating a new namespace when a new top-level API category is +introduced. ## Issues and Considerations @@ -47,33 +51,78 @@ and libraries and limits length of IDE prompts such as Intellisense. 2. Namespace must be consistent with API navigation path. -3. When type name is ambiguous and requires a namespace qualifier changing namespace is a breaking change. - +3. When type name is ambiguous and requires a namespace qualifier, changing + namespace is a breaking change. 4. To extend a type in a different schema, a service must declare that schema and the type in it. This is conceptually similar to .NET partial types. 5. To reference a type in a different schema, simply refer to that type by fully qualified name (namespace + type name). -6. Microsoft Graph has heuristic rules for declared namespaces: - - All public namespaces must have a prefix ‘microsoft.graph’ +6. Cyclical references between namespaces are not allowed as many + object-oriented languages don’t support a cycle in inheritance. + +7. Microsoft Graph has heuristic rules for declared namespaces: - - If a namespace does not begin with ‘microsoft.graph’ prefix, all types in - the schema will be coerced into the main ‘microsoft.graph’ namespace. + 1. All public namespaces must have a prefix ‘microsoft.graph’ + + 2. If a namespace does not begin with ‘microsoft.graph’ prefix, all types + in the schema will be coerced into the main ‘microsoft.graph’ namespace. ## Example -Namespace and type declarations: -```XML +### Namespace and type declarations: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ XML ”\> … - -``` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Fully qualified type name: microsoft.graph.search.bookmark + +### Managing multiple schemas: + +Workloads must define schemas in their csdl using the Edmx format. +[Microsoft.IC3.DataPlatform](https://dev.azure.com/msazure/One/_git/AD-AggregatorService-Workloads?path=%2FWorkloads%2FMicrosoft.IC3.DataPlatform%2Foverride%2Fschema-Prod-beta.csdl) +is an example of a workload that exposes multiple namespaces. + +**Tip:** As with schemas that exist in the microsoft.graph namespace, defining an +entity type is optional, AGS will transform your schema to make all entity types +derive from microsoft.graph.entity. + +**Warning:** Do not deviate from the general structure in the example below. +Schema validation tool expects the XML structure (including xml namespace +declarations) to match the example below. +```XML + + + + + + + + + + + + + + + + + + + + + + + + + + +``` From 7008f9638f10025fbbe556547199a4fcc82f7837 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 15 Mar 2022 10:03:26 -0400 Subject: [PATCH 297/729] - adds delta for navigation properties and removed --- graph/patterns/change-tracking.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index b6166f15..5ee9705c 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -23,6 +23,17 @@ This new endpoint can be used to sync API consumers. This is achieved through re 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...) +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. + +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. +- The reason MUST be set to `changed` if the entity can be restored. `"@removed": {"reason": "changed"}`. +- The reason MUST be set to `deleted` if the entity cannot be restored. `"@removed": {"reason": "deleted"}`. + +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): + +- When a user is added to the group `"members@delta": ["id of the added user"]` +- When a user is removed from the group, or the target user is deleted `"members@delta": ["id of the added user"]` + > 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. ## When to Use this Pattern @@ -37,7 +48,6 @@ Before using the change tracking pattern in your API definition, make sure your ### Alternatives - Change notifications pattern (TODO add link when described) -- Export pattern (TODO) - Backup pattern (TODO) ## Examples From cde128a7ca929c91b15cf2733642cebebb8eb255 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 15 Mar 2022 10:06:32 -0400 Subject: [PATCH 298/729] - fixed property delta syntax --- graph/patterns/change-tracking.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 5ee9705c..0c33f5fb 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -31,8 +31,8 @@ When an entity is soft deleted, the delta function MUST return the id of the del 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): -- When a user is added to the group `"members@delta": ["id of the added user"]` -- When a user is removed from the group, or the target user is deleted `"members@delta": ["id of the added user"]` +- When a user is added to the group `"members@delta": [{ "@odata.type": "#microsoft.graph.user", "id of the added user"}]` +- 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"}]` > 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. From a53aa654d676cfde5122573cd941ddf53996b9a8 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 21 Mar 2022 09:05:37 -0500 Subject: [PATCH 299/729] Update guideline for date values in headers to follow RFC 7231 --- azure/Guidelines.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index db8b07f3..63a93da3 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -160,7 +160,7 @@ Integer | -253+1 to +253-1 (for consistency with JSON li Float | [IEEE-754 binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) String | (Un)quoted?, max length, legal characters, case-sensitive, multiple delimiter UUID | 123e4567-e89b-12d3-a456-426614174000 (no {}s, hyphens, case-insensitive) [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) -Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT [RFC 1123, Section 5.2.14](https://datatracker.ietf.org/doc/html/rfc1123#page-55) +Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ (with at most 3 digits of fractional seconds) [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) Byte array | Base-64 encoded, max length Array | One of a) a comma-separated list of values (preferred), or b) separate `name=value` parameter instances for each value of the array @@ -196,9 +196,9 @@ retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://da :white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it -:white_check_mark: **DO** accept and return date values in headers using the HTTP Date format as defined in [RFC 1123, Section 5.2.14](https://datatracker.ietf.org/doc/html/rfc1123#page-55), e.g. "Sun, 06 Nov 1994 08:49:37 GMT". +:white_check_mark: **DO** accept date values in headers in HTTP-Date format and return date values in headers in the IMF-fixdate format as defined in [RFC7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT". -Note: RFC 1123 defines the date format as a modification of the date format in [RFC 822, Section 5](https://datatracker.ietf.org/doc/html/rfc822#section-5) to support either a 2 or 4 digit year, and further recommends that a 4 digit year always be used. +Note: The RFC 7321 IMF-fixdate format is a "fixed-length and single-zone subset" of the RFC 1123 / RFC 5822 format, which means: a) year must be four digits, b) the seconds component of time is required, and c) the timezone must be GMT. :white_check_mark: **DO** create an opaque value that uniquely identifies the request and return this value in the `x-ms-request-id` response header. From bd7b1d9affe624a1af8bc7e2f091bd797891ca9b Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 21 Mar 2022 11:30:58 -0500 Subject: [PATCH 300/729] Passel of structure, formmatting, and link fixes --- azure/Guidelines.md | 52 ++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 63a93da3..0627a8f5 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -46,12 +46,12 @@ The Microsoft Azure Cloud platform exposes its APIs through the core building bl ### HTTP Azure services must adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231). This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: -- [Uniform Resource Locators (URLs)](URLS) -- HTTP Methods -- Request & Response Headers -- Bodies -### Uniform Resource Locators (URLs) +- Uniform Resource Locators (URLs) +- HTTP Request / Response Pattern +- HTTP Query Parameters and Header Values + +#### Uniform Resource Locators (URLs) A Uniform Resource Locator (URL) is how developers access the resources of your service. Ultimately, URLs are how developers form a cognitive model of your service's resources. @@ -88,8 +88,6 @@ Some customer-provided path segment values may be compared case-insensitivity if :heavy_check_mark: **YOU MAY** use these other characters in the URL path but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` -#### Direct Endpoint URLs - :heavy_check_mark: **YOU MAY** support a direct endpoint URL for performance/routing: ```text https://-../... @@ -103,16 +101,16 @@ Examples: :white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for `` or always a single verified domain. :heavy_check_mark: **YOU MAY** use URLs as values -```http +```text https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy ``` -### HTTP Request / Response Pattern +#### HTTP Request / Response Pattern The HTTP Request / Response pattern dictates how your API behaves. For example: POST methods that create resources must be idempotent, GET method results may be cached, the If-Modified and ETag headers offer optimistic concurrency. The URL of a service, along with its request/response bodies, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, _all_ service operations (including POST) **must** be idempotent. Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences. -#### Exactly Once Behavior = Client Retries & Service Idempotency +##### Exactly Once Behavior = Client Retries & Service Idempotency :white_check_mark: **DO** ensure that _all_ HTTP methods are idempotent. @@ -146,7 +144,7 @@ DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` :white_check_mark: **DO** support caching and optimistic concurrency by honoring the the `If-Match`, `If-None-Match`, if-modified-since, and if-unmodified-since request headers and by returning the ETag and last-modified response headers -### HTTP Query Parameters and Header Values +#### HTTP Query Parameters and Header Values Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. :white_check_mark: **DO** validate all query parameter and request header values and fail the operation with `400-Bad Request` if any value fails validation. Return an error response as described in the [Handling Errors](#Handling-errors) section indicating what is wrong so customer can diagnose the issue and fix it themselves. @@ -215,7 +213,7 @@ Your service should include the `x-ms-request-id` value in error logs so that us ### REpresentational State Transfer (REST) REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. -These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#URLS) determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. +These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#uniform-resource-locators-urls) determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. See [this article in the Azure Architecture Center](https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design) for a more detailed discussion of REST API design patterns. When designing your service, it is important to optimize for the developer using your API. @@ -444,23 +442,23 @@ The REST specification is used to model the state of a resource, and is primaril :ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a resource **URL Pattern** -```http +```text https://...//:? ``` **Example** -```http +```text https://.../users/Bob:grant?access=read ``` :ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a collection **URL Pattern** -```http +```text https://.../:? ``` **Example** -```http +```text https://.../users:grant?access=read ``` @@ -542,7 +540,7 @@ The value of the `filter` option is an expression involving the fields of the re Example: return all Products whose Price is less than $10.00 -```http +```text GET https://api.contoso.com/products?`filter`=price lt 10.00 ``` @@ -566,7 +564,7 @@ not | Logical negation | not price le 3.5 **Grouping Operators** | | ( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 -:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](handling-errors) section if a client includes an operator in a `filter` expression that is not supported by the operation. +:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#handling-errors) section if a client includes an operator in a `filter` expression that is not supported by the operation. :white_check_mark: **DO** use the following operator precedence for supported operators when evaluating `filter` expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: @@ -590,31 +588,31 @@ The following examples illustrate the use and semantics of each of the logical o Example: all products with a name equal to 'Milk' -```http +```text GET https://api.contoso.com/products?`filter`=name eq 'Milk' ``` Example: all products with a name not equal to 'Milk' -```http +```text GET https://api.contoso.com/products?`filter`=name ne 'Milk' ``` Example: all products with the name 'Milk' that also have a price less than 2.55: -```http +```text GET https://api.contoso.com/products?`filter`=name eq 'Milk' and price lt 2.55 ``` Example: all products that either have the name 'Milk' or have a price less than 2.55: -```http +```text GET https://api.contoso.com/products?`filter`=name eq 'Milk' or price lt 2.55 ``` Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: -```http +```text GET https://api.contoso.com/products?`filter`=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 ``` @@ -639,17 +637,17 @@ Each expression in the `orderby` parameter value may include the suffix "asc" fo :white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#Handling-errors) section if the client requests sorting by a field that is not supported by the operation. For example, to return all people sorted by name in ascending order: -```http +```text GET https://api.contoso.com/people?orderby=name ``` For example, to return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. -```http +```text GET https://api.contoso.com/people?orderby=name desc,hireDate ``` Sorting MUST compose with `filter`ing such that: -```http +```text GET https://api.contoso.com/people?`filter`=name eq 'david'&orderby=hireDate ``` will return all people whose name is David sorted in ascending order by hireDate. @@ -691,7 +689,7 @@ Azure services need to change over time. However, when changing a service, there :white_check_mark: **DO** use an `api-version` query parameter with a `YYYY-MM-DD` date value, with a `-preview` suffix for a preview service. -```http +```text PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 ``` From ce1d739f0481ce75fc668bf67ca1715a79ca9bf8 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 23 Mar 2022 15:12:15 -0500 Subject: [PATCH 301/729] Address PR review comments --- azure/Guidelines.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 0627a8f5..2b320f86 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -47,9 +47,9 @@ The Microsoft Azure Cloud platform exposes its APIs through the core building bl ### HTTP Azure services must adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231). This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: -- Uniform Resource Locators (URLs) -- HTTP Request / Response Pattern -- HTTP Query Parameters and Header Values +- [Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) +- [HTTP Request / Response Pattern](#http-request--response-pattern) +- [HTTP Query Parameters and Header Values](#http-query-parameters-and-header-values) #### Uniform Resource Locators (URLs) From 8121816dbf2a1018fc96dd3b33b3e9619745e6f5 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 25 Mar 2022 16:05:03 -0400 Subject: [PATCH 302/729] Added date timeline Added revision to the timeline in the front --- azure/Guidelines.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 2b320f86..ce7c7b65 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -3,7 +3,8 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | -| 2022-Feb-01 | Updated error guidance | +| 2022-Mar-25 | Update guideline for date values in headers to follow RFC 7231 | +| 2022-Feb-01 | Updated error guidance | | 2021-Sep-11 | Add long-running operations guidance | | 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | | 2020-Jul-31 | Added service advice for initial versions | From 0f714c2a34871c614d1e5f0c63007eaeee3d9044 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Sun, 27 Mar 2022 17:01:24 -0400 Subject: [PATCH 303/729] Removed partitioning --- graph/patterns/namespace.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 79265fa4..1938a0cd 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -2,20 +2,19 @@ Microsoft Graph API Design Pattern -### *The Namespace provides the ability to group resource definitions together into a logical set.* +### *The Namespace provides the ability to organize resource definitions together into a logical set.* ## Problem When building a complex offering API designers may need to model many different resources and their relationships. For better user experience and -discoverability related API definitions need to be grouped together. Large API -surface may need to be partitioned to generate light-weight client SDKs and -libraries. +discoverability related API elements need to be clustered together. + ## Solution API designers can use the Namespace attribute of the CSDL schema to declare a -namespace and logically bundle related API entities in the Graph metadata. +namespace and logically organize related API entities in the Graph metadata. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ XML @@ -23,15 +22,12 @@ namespace and logically bundle related API entities in the Graph metadata. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -All resources declared within the namespace will be grouped together in the -public Graph metadata and may be used for partitioning. - A public namespace must have "microsoft.graph.” prefix and be presented in camel case, i.e microsoft.graph.myNamespace. -When type casting is required in the API query, request or response a fully +When type casting is required in the API query, request or response, a fully qualified type name is represented as concatenation of a namespace and a type -name. Consequently namespace should be aligned with API category path segment. +name. Consequently namespaces should be aligned with API category path segment. ## When to Use this Pattern From d107f8556afb9771ec0e8caecc859a598d2a7485 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 28 Mar 2022 07:48:05 -0500 Subject: [PATCH 304/729] Add guidance for count field in pageable response --- azure/Guidelines.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ce7c7b65..5076bd01 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -506,7 +506,9 @@ Note: To avoid potential collision of actions and resource ids, you should disal :no_entry: **DO NOT** return the `nextLink` field at all when returning the last page of the collection. -:no_entry: **DO NOT** ever return a `nextLink` field with a value of null. +:no_entry: **DO NOT** return the `nextLink` field with a value of null. + +:warning: **YOU SHOULD NOT** return a `count` of all objects in the collection as this may be expensive to compute. #### Query options :heavy_check_mark: **YOU MAY** support the following query parameters allowing customers to control the list operation: From 297b0a5571f016c0f4c99ac28a405641da38f173 Mon Sep 17 00:00:00 2001 From: tg-msft Date: Mon, 28 Mar 2022 15:49:28 -0700 Subject: [PATCH 305/729] Reorder the guidelines so the error contract is clearer --- azure/Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ce7c7b65..63922967 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -302,8 +302,6 @@ There are 2 kinds of errors: :white_check_mark: **DO** ensure that the top-level error's `code` value is identical to the `x-ms-error-code` header's value. -:white_check_mark: **DO** document the service's error code strings; they are part of the API contract. - :white_check_mark: **DO** provide a response body with the following structure: **ErrorResponse** : Object @@ -347,6 +345,8 @@ Example: :heavy_check_mark: **YOU MAY** group common customer code errors into a few `x-ms-error-code` string values. +:white_check_mark: **DO** document the service's top-level error code strings; they are part of the API contract. + :heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are _not_ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. :heavy_check_mark: **YOU MAY** add additional properties for any data values in your error message so customers don't resort to parsing your error message. For example, an error with `"message": "A maximum of 16 keys are allowed per account."` might also add a `"maximumKeys": 16` property. This is not part of your API contract and should only be used for diagnosing problems. From 49bcdfbf5fec21d86ae824c0c0e0c7fc9ae8b859 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Tue, 29 Mar 2022 14:46:56 -0700 Subject: [PATCH 306/729] Add guidelines for durations --- azure/Guidelines.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ce7c7b65..a6f591d1 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -3,6 +3,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2022-Mar-29 | Add guidelines about using durations | | 2022-Mar-25 | Update guideline for date values in headers to follow RFC 7231 | | 2022-Feb-01 | Updated error guidance | | 2021-Sep-11 | Add long-running operations guidance | @@ -374,6 +375,10 @@ Services, and the clients that access them, may be written in multiple languages :white_check_mark: **DO** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) for date/time. +:white_check_mark: **DO** use a fixed time interval when using durations e.g., `ttlSeconds`. The unit of time should be part of the name, but must at least be documented in the property description. + +:heavy_check_mark: **YOU MAY** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) durations only when users must be able to specify a time intervals that may change from month to month or year to year e.g., "P1Y" represents 366 days on a leap year. + :white_check_mark: **DO** use [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) for UUIDs. :heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. From fadddfa90549ed22eaace22ea1a5c97e70431789 Mon Sep 17 00:00:00 2001 From: tg-msft Date: Tue, 29 Mar 2022 17:19:45 -0700 Subject: [PATCH 307/729] PR feedback --- azure/Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 63922967..51cdd02e 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -300,6 +300,8 @@ There are 2 kinds of errors: :white_check_mark: **DO** carefully craft unique `x-ms-error-code` string values for errors that are recoverable at runtime. Reuse common error codes for usage errors that are not recoverable. +:heavy_check_mark: **YOU MAY** group common customer code errors into a few `x-ms-error-code` string values. + :white_check_mark: **DO** ensure that the top-level error's `code` value is identical to the `x-ms-error-code` header's value. :white_check_mark: **DO** provide a response body with the following structure: @@ -343,8 +345,6 @@ Example: } ``` -:heavy_check_mark: **YOU MAY** group common customer code errors into a few `x-ms-error-code` string values. - :white_check_mark: **DO** document the service's top-level error code strings; they are part of the API contract. :heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are _not_ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. From f1f871de94fedd58756a12a3b882bf836ee4b2a9 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Wed, 30 Mar 2022 11:40:52 -0700 Subject: [PATCH 308/729] Resolve PR feedback --- azure/Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a6f591d1..6c5afc5d 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -375,9 +375,9 @@ Services, and the clients that access them, may be written in multiple languages :white_check_mark: **DO** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) for date/time. -:white_check_mark: **DO** use a fixed time interval when using durations e.g., `ttlSeconds`. The unit of time should be part of the name, but must at least be documented in the property description. +:white_check_mark: **DO** use a fixed time interval when using durations and include the time unit in the name e.g., `backupTimeInMinutes` or `ttlSeconds`. The time unit should also be included in the property description to support localization e.g., "How often to back up expressed in minutes," or "The lifetime of the resource expressed in seconds." -:heavy_check_mark: **YOU MAY** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) durations only when users must be able to specify a time intervals that may change from month to month or year to year e.g., "P1Y" represents 366 days on a leap year. +:heavy_check_mark: **YOU MAY** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) durations only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P1Y" represents 366 days on a leap year. :white_check_mark: **DO** use [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) for UUIDs. From be8cd01f81c22a37044ad8bdd518b12558fa82ea Mon Sep 17 00:00:00 2001 From: Weidong Xu Date: Thu, 31 Mar 2022 16:32:03 +0800 Subject: [PATCH 309/729] Update Codeowners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 01ea4ac9..06bf5c02 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # These are the set of folks who should review PRs on the azureRestUpdates branch. -* @microsoft/azure-api-stewardship-board \ No newline at end of file +* @microsoft/azure-api-stewardship-board @Azure/api-stewardship-board From 49a77ae591cc5a78c6bb7f798936154045393c2a Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Thu, 31 Mar 2022 13:13:20 -0700 Subject: [PATCH 310/729] Resolve PR feedback Co-authored-by: Jeffrey Richter --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 6c5afc5d..fc271691 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -375,7 +375,7 @@ Services, and the clients that access them, may be written in multiple languages :white_check_mark: **DO** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) for date/time. -:white_check_mark: **DO** use a fixed time interval when using durations and include the time unit in the name e.g., `backupTimeInMinutes` or `ttlSeconds`. The time unit should also be included in the property description to support localization e.g., "How often to back up expressed in minutes," or "The lifetime of the resource expressed in seconds." +:white_check_mark: **DO** use a fixed time interval to express durations and include the time unit in the property name e.g., `backupTimeInMinutes` or `ttlSeconds`. The time unit should also be included in the property description to support localization e.g., "How often to back up expressed in minutes," or "The lifetime of the resource expressed in seconds." :heavy_check_mark: **YOU MAY** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) durations only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P1Y" represents 366 days on a leap year. From d3a6f71da616b90555d80b4cbba059e36261b489 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Mon, 4 Apr 2022 09:58:56 -0700 Subject: [PATCH 311/729] Resolve PR feedback --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index fc271691..088f6960 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -375,7 +375,7 @@ Services, and the clients that access them, may be written in multiple languages :white_check_mark: **DO** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) for date/time. -:white_check_mark: **DO** use a fixed time interval to express durations and include the time unit in the property name e.g., `backupTimeInMinutes` or `ttlSeconds`. The time unit should also be included in the property description to support localization e.g., "How often to back up expressed in minutes," or "The lifetime of the resource expressed in seconds." +:white_check_mark: **DO** use a fixed time interval to express durations and include the time unit in the property name e.g., `backupTimeInMinutes` or `ttlSeconds`. :heavy_check_mark: **YOU MAY** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) durations only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P1Y" represents 366 days on a leap year. From dcba8e234bfbef64865de37a6acf8b1eb191bd97 Mon Sep 17 00:00:00 2001 From: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> Date: Mon, 4 Apr 2022 14:38:56 -0700 Subject: [PATCH 312/729] Document evolvable enum design pattern --- graph/patterns/evolvable-enums.md | 302 ++++++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 graph/patterns/evolvable-enums.md diff --git a/graph/patterns/evolvable-enums.md b/graph/patterns/evolvable-enums.md new file mode 100644 index 00000000..6e17d1d1 --- /dev/null +++ b/graph/patterns/evolvable-enums.md @@ -0,0 +1,302 @@ +# Evolvable Enums + +Microsoft Graph API Design Pattern + +*The evolvable enum pattern allows API producers to extend enumerated types with new members without breaking API consumers.* + +## Problem + +--- +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. + +## Solution + +--- +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. + +## When to Use this Pattern + +--- + +- 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. + +- 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. + +## Issues and Considerations + +--- + +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. + +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. + +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. + +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. + +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. + +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. + +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. + +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. + +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. + +For details of how the ```unknownFutureValue``` value is handled as part of a ```$filter``` clause please consult the following examples. + +### CSDL + +```xml + + + + + + + + + + +``` + +### Filter Behavior + +| ```$filter``` clause | ```Preference: include-unknown-enum-members``` Absent | ```Preference: include-unknown-enum-members``` Present | +|---|---|---| +| ```enumProperty eq unknownFutureValue```| Return entities where enumProperty has any value greater than ```unknownFutureValue``` replacing actual value with ```unknownFutureValue```| Return nothing | +| ```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``` | +| ```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```| +| ```enumProperty eq newValue``` | ```400 Bad Request``` | Return entities where enumProperty has the value ```newValue``` | +| ```enumProperty gt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value greater than ```newValue``` | +| ```enumProperty lt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value less than ```newValue``` | + +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. + +## Examples + +--- + +For the following examples we will consider the ```managedDevice``` entity which refers to the ```managedDeviceArchitecture``` enum type. + +```xml + + + + + +``` + +When the ```managedDeviceArchitecture``` enum was initially published to Graph it was defined as below: + +```xml + + + + + + + + + +``` + +The enum was later extended to add a new value of ```quantum``` leading to the below CSDL + +```xml + + + + + + + + + +``` + +### Default Behavior + +```http +GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture +``` + +```json +{ + "value": [ + { + "id": "0", + "displayName": "Surface Pro X", + "processorArchitecture" : "arm64" + }, + { + "id": "1", + "displayName": "Prototype", + "processorArchitecture": "unknownFutureValue" + } + { + "id": "2", + "displayName": "My Laptop", + "processorArchitecture": "x64" + } + ] +} +``` + +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``` + +### Include opt-in Header + +```http +GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture + +Preference: include-unknown-enum-members +``` + +```json +{ + "value": [ + { + "displayName": "Surface Pro X", + "processorArchitecture" : "arm64" + }, + { + "displayName": "Prototype", + "processorArchitecture": "quantum" + }, + { + "displayName": "My Laptop", + "processorArchitecture": "x64" + } + ] +} +``` + +### Default Sort Behavior + +```http +GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$orderBy=processorArchitecture +``` + +```json +{ + "value": [ + { + "displayName": "Surface Pro X", + "processorArchitecture" : "arm64" + }, + { + "displayName": "My Laptop", + "processorArchitecture": "x64" + }, + { + "displayName": "Prototype", + "processorArchitecture": "unknownFutureValue" + } + ] +} +``` + +### Sort Behavior with opt-in Header + +```http +GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture + +Preference: include-unknown-enum-members +``` + +```json +{ + "value": [ + { + "displayName": "Surface Pro X", + "processorArchitecture" : "arm64" + }, + { + "displayName": "My Laptop", + "processorArchitecture": "x64" + }, + { + "displayName": "Prototype", + "processorArchitecture": "quantum" + } + ] +} +``` + +### Default Filter Behavior + +```http +GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$filter=processorArchitecture gt x64 +``` + +```json +{ + "value": [ + { + "displayName": "My Laptop", + "processorArchitecture": "x64" + }, + { + "displayName": "Prototype", + "processorArchitecture": "unknownFutureValue" + } + ] +} +``` + +### Filter Behavior with opt-in Header + +```http +GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$filter=processorArchitecture gt x64 + +Preference: include-unknown-enum-members +``` + +```json +{ + "value": [ + { + "displayName": "My Laptop", + "processorArchitecture": "x64" + }, + { + "displayName": "Prototype", + "processorArchitecture": "quantum" + } + ] +} +``` + +### Patch Example + +```http +PATCH https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/1 + +{ + "displayName": "Secret Prototype", + "processorArchitecture": "unknownFutureValue" +} +``` + +```json +{ + "id": "1", + "displayName": "Secret Prototype", + "processorArchitecture": "unknownFutureValue" +} +``` + +```http +GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/1 +Preference: include-unknown-enum-members +``` + +```json +{ + "id": "1", + "displayName": "Secret Prototype", + "processorArchitecture": "quantum" +} +``` From 5827babb323522dd7073f97f30a19f52b7944a48 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 5 Apr 2022 11:51:12 -0400 Subject: [PATCH 313/729] additional clarifications --- graph/patterns/namespace.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 1938a0cd..a85320aa 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -8,7 +8,7 @@ Microsoft Graph API Design Pattern When building a complex offering API designers may need to model many different resources and their relationships. For better user experience and -discoverability related API elements need to be clustered together. +discoverability related API elements need to be grouped together. ## Solution @@ -27,13 +27,14 @@ case, i.e microsoft.graph.myNamespace. When type casting is required in the API query, request or response, a fully qualified type name is represented as concatenation of a namespace and a type -name. Consequently namespaces should be aligned with API category path segment. +name. For consistent user experience namespaces should be aligned with the corresponding API category path segment. + ## When to Use this Pattern API resource grouping creates a user-friendly experience keeping all resources -for a specific feature close together. It also allows generating smaller SDKs -and libraries and limits length of IDE prompts such as Intellisense. +for a specific feature close together. It also allows generating smallersmaller downloadable client components +and limits length of IDE prompts such as Intellisense. We recommend creating a new namespace when a new top-level API category is introduced. @@ -42,10 +43,10 @@ introduced. 1. Microsoft Graph consistency requirements discourage using the same type names for different concepts even within different namespaces. Microsoft - Graph type names must be descriptive and almost always unique within the API - surface. + Graph type names must be descriptive and unique within the API + surface without requiring full qualification. -2. Namespace must be consistent with API navigation path. +2. Namespace must be consistent with API category in the navigation path according to [Microsoft Graph REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/graph/GuidelinesGraph.md#uniform-resource-locators-urls) 3. When type name is ambiguous and requires a namespace qualifier, changing namespace is a breaking change. From 8e7e24f34c88334246d904650167d9529429b246 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 5 Apr 2022 12:28:54 -0400 Subject: [PATCH 314/729] article --- graph/patterns/namespace.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index a85320aa..9dc04c4d 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -34,7 +34,7 @@ name. For consistent user experience namespaces should be aligned with the corre API resource grouping creates a user-friendly experience keeping all resources for a specific feature close together. It also allows generating smallersmaller downloadable client components -and limits length of IDE prompts such as Intellisense. +and limits the length of IDE prompts such as Intellisense. We recommend creating a new namespace when a new top-level API category is introduced. From e302b76a5b688c94c54b23f519e38015c2624d16 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 5 Apr 2022 12:31:43 -0400 Subject: [PATCH 315/729] nit --- graph/patterns/namespace.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 9dc04c4d..5ab70ebc 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -67,7 +67,7 @@ introduced. 2. If a namespace does not begin with ‘microsoft.graph’ prefix, all types in the schema will be coerced into the main ‘microsoft.graph’ namespace. -## Example +## Examples ### Namespace and type declarations: From d1126890ad65142ba835cbc9ad327a1691b13618 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Tue, 5 Apr 2022 13:03:20 -0700 Subject: [PATCH 316/729] Resolve PR feedback --- azure/Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 088f6960..78a80e61 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -375,9 +375,9 @@ Services, and the clients that access them, may be written in multiple languages :white_check_mark: **DO** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) for date/time. -:white_check_mark: **DO** use a fixed time interval to express durations and include the time unit in the property name e.g., `backupTimeInMinutes` or `ttlSeconds`. +:white_check_mark: **DO** use a fixed time interval to express durations e.g., milliseconds, seconds, minutes, days, etc., and include the time unit in the property name e.g., `backupTimeInMinutes` or `ttlSeconds`. -:heavy_check_mark: **YOU MAY** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) durations only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P1Y" represents 366 days on a leap year. +:heavy_check_mark: **YOU MAY** use [RFC3339 durations](https://wikipedia.org/wiki/ISO_8601#Durations) only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P3M" represents 3 months no matter how many days between the start and end, or "P1Y" represents 366 days on a leap year. :white_check_mark: **DO** use [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) for UUIDs. From 9c109ed80e292a4e3e3215034ee5409b762e5afb Mon Sep 17 00:00:00 2001 From: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> Date: Tue, 5 Apr 2022 13:20:20 -0700 Subject: [PATCH 317/729] Apply suggestions from code review Co-authored-by: Dan Kershaw [MSFT] --- graph/patterns/evolvable-enums.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/graph/patterns/evolvable-enums.md b/graph/patterns/evolvable-enums.md index 6e17d1d1..d2be5c01 100644 --- a/graph/patterns/evolvable-enums.md +++ b/graph/patterns/evolvable-enums.md @@ -12,7 +12,7 @@ Frequently API producers want to add new members to an enum type after it is ini ## Solution --- -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. +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-members``` HTTP header in the response. ## When to Use this Pattern @@ -155,6 +155,8 @@ Preference: include-unknown-enum-members ``` ```json +Preference-Applied: include-unknown-enum-members + { "value": [ { @@ -207,6 +209,8 @@ Preference: include-unknown-enum-members ``` ```json +Preference-Applied: include-unknown-enum-members + { "value": [ { @@ -255,6 +259,8 @@ Preference: include-unknown-enum-members ``` ```json +Preference-Applied: include-unknown-enum-members + { "value": [ { @@ -294,6 +300,8 @@ Preference: include-unknown-enum-members ``` ```json +Preference-Applied: include-unknown-enum-members + { "id": "1", "displayName": "Secret Prototype", From 0f764ae8d983e9349e711de52479a661e0c2ef95 Mon Sep 17 00:00:00 2001 From: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> Date: Tue, 5 Apr 2022 13:29:50 -0700 Subject: [PATCH 318/729] Add error case for API consumer specifying new values without opting in --- graph/patterns/evolvable-enums.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graph/patterns/evolvable-enums.md b/graph/patterns/evolvable-enums.md index d2be5c01..5ec9c398 100644 --- a/graph/patterns/evolvable-enums.md +++ b/graph/patterns/evolvable-enums.md @@ -44,6 +44,8 @@ If an API consumer specifies ```unknownFutureValue``` for the value of a propert 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. +If an API consumer specifies an enum member greater than ```unknownFutureValue``` in any request without specifying the ```Preference: include-unknown-enum-members``` header the API producer must reject the request with a ```400 Bad Request``` HTTP status. + For details of how the ```unknownFutureValue``` value is handled as part of a ```$filter``` clause please consult the following examples. ### CSDL From 86b3dbddfdca6a7bea35cfc4fe68df25427b9849 Mon Sep 17 00:00:00 2001 From: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> Date: Wed, 6 Apr 2022 09:16:12 -0700 Subject: [PATCH 319/729] Fix name of header --- graph/patterns/evolvable-enums.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/graph/patterns/evolvable-enums.md b/graph/patterns/evolvable-enums.md index 5ec9c398..478afd94 100644 --- a/graph/patterns/evolvable-enums.md +++ b/graph/patterns/evolvable-enums.md @@ -12,7 +12,7 @@ Frequently API producers want to add new members to an enum type after it is ini ## Solution --- -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-members``` HTTP header in the response. +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 ```Prefer: 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-members``` HTTP header in the response. ## When to Use this Pattern @@ -34,7 +34,7 @@ Enum Types can have multiple members with the same numeric value to allow for al 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. -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. +The ```Prefer: 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. 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. @@ -44,7 +44,7 @@ If an API consumer specifies ```unknownFutureValue``` for the value of a propert 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. -If an API consumer specifies an enum member greater than ```unknownFutureValue``` in any request without specifying the ```Preference: include-unknown-enum-members``` header the API producer must reject the request with a ```400 Bad Request``` HTTP status. +If an API consumer specifies an enum member greater than ```unknownFutureValue``` in any request without specifying the ```Prefer: include-unknown-enum-members``` header the API producer must reject the request with a ```400 Bad Request``` HTTP status. For details of how the ```unknownFutureValue``` value is handled as part of a ```$filter``` clause please consult the following examples. @@ -65,7 +65,7 @@ For details of how the ```unknownFutureValue``` value is handled as part of a `` ### Filter Behavior -| ```$filter``` clause | ```Preference: include-unknown-enum-members``` Absent | ```Preference: include-unknown-enum-members``` Present | +| ```$filter``` clause | ```Prefer: include-unknown-enum-members``` Absent | ```Prefer: include-unknown-enum-members``` Present | |---|---|---| | ```enumProperty eq unknownFutureValue```| Return entities where enumProperty has any value greater than ```unknownFutureValue``` replacing actual value with ```unknownFutureValue```| Return nothing | | ```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``` | @@ -74,7 +74,7 @@ For details of how the ```unknownFutureValue``` value is handled as part of a `` | ```enumProperty gt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value greater than ```newValue``` | | ```enumProperty lt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value less than ```newValue``` | -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. +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 ```Prefer: include-unknown-enum-members``` header is absent. ## Examples @@ -153,7 +153,7 @@ In this case the value of the ```processorArchitecture``` property is ```quantum ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture -Preference: include-unknown-enum-members +Prefer: include-unknown-enum-members ``` ```json @@ -207,7 +207,7 @@ GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=dis ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture -Preference: include-unknown-enum-members +Prefer: include-unknown-enum-members ``` ```json @@ -257,7 +257,7 @@ GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=dis ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$filter=processorArchitecture gt x64 -Preference: include-unknown-enum-members +Prefer: include-unknown-enum-members ``` ```json @@ -298,7 +298,7 @@ PATCH https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/1 ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/1 -Preference: include-unknown-enum-members +Prefer: include-unknown-enum-members ``` ```json From 10d1589647dac361161e97d1ecee016e816b08d3 Mon Sep 17 00:00:00 2001 From: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> Date: Wed, 6 Apr 2022 09:27:43 -0700 Subject: [PATCH 320/729] Update graph/patterns/evolvable-enums.md Co-authored-by: Darrel --- graph/patterns/evolvable-enums.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/evolvable-enums.md b/graph/patterns/evolvable-enums.md index 478afd94..d3944704 100644 --- a/graph/patterns/evolvable-enums.md +++ b/graph/patterns/evolvable-enums.md @@ -18,7 +18,7 @@ The solution here is to add a 'sentinel' member named ```unknownFutureValue``` a --- -- 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. +- 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 require extending the enum. - 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. From 28f0d597f1b9cd8822f45fe0a189512067a26875 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 6 Apr 2022 12:50:15 -0400 Subject: [PATCH 321/729] Update graph/patterns/namespace.md Co-authored-by: Darrel --- graph/patterns/namespace.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 5ab70ebc..0a9a68ab 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -27,7 +27,7 @@ case, i.e microsoft.graph.myNamespace. When type casting is required in the API query, request or response, a fully qualified type name is represented as concatenation of a namespace and a type -name. For consistent user experience namespaces should be aligned with the corresponding API category path segment. +name. For consistent user experience namespaces MUST be aligned with the corresponding API category path segment. ## When to Use this Pattern From 7e4ec00a3399b206b696d9b5b90dc03f96288c66 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 6 Apr 2022 13:02:22 -0400 Subject: [PATCH 322/729] Update graph/patterns/namespace.md Co-authored-by: Darrel --- graph/patterns/namespace.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 0a9a68ab..44242d95 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -34,7 +34,7 @@ name. For consistent user experience namespaces MUST be aligned with the corresp API resource grouping creates a user-friendly experience keeping all resources for a specific feature close together. It also allows generating smallersmaller downloadable client components -and limits the length of IDE prompts such as Intellisense. +and limits the length of IDE prompts such as auto-complete in some programming languages. We recommend creating a new namespace when a new top-level API category is introduced. From 9dc10eb0db041d85a834e3979210381968f90536 Mon Sep 17 00:00:00 2001 From: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> Date: Wed, 6 Apr 2022 10:36:13 -0700 Subject: [PATCH 323/729] Add example for flag enum --- graph/patterns/evolvable-enums.md | 202 ++++++++++++++++++++++++++++-- 1 file changed, 190 insertions(+), 12 deletions(-) diff --git a/graph/patterns/evolvable-enums.md b/graph/patterns/evolvable-enums.md index d3944704..074aa42e 100644 --- a/graph/patterns/evolvable-enums.md +++ b/graph/patterns/evolvable-enums.md @@ -26,27 +26,29 @@ The solution here is to add a 'sentinel' member named ```unknownFutureValue``` a --- -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. +- 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. -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. +- Changing the value (i.e. position) of the ```unknownFutureValue``` sentinel member is considered a breaking change, and must follow the [deprecation](../deprecation.md) process. -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. +- 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. -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. +- 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. -The ```Prefer: 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. +- The ```Prefer: 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. -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. +- 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. -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. +- 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. -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. +- If the value of a property containing a flag enum contains multiple unknown values they should all be replaced with a single ```unknownFutureValue``` value (i.e. there should not have multiple ```unknownFutureValue``` values returned). -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. +- 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. -If an API consumer specifies an enum member greater than ```unknownFutureValue``` in any request without specifying the ```Prefer: include-unknown-enum-members``` header the API producer must reject the request with a ```400 Bad Request``` HTTP status. +- 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. -For details of how the ```unknownFutureValue``` value is handled as part of a ```$filter``` clause please consult the following examples. +- If an API consumer specifies an enum member greater than ```unknownFutureValue``` in any request without specifying the ```Prefer: include-unknown-enum-members``` header the API producer must reject the request with a ```400 Bad Request``` HTTP status. + +- For details of how the ```unknownFutureValue``` value is handled as part of a ```$filter``` clause please consult the following examples: ### CSDL @@ -74,7 +76,7 @@ For details of how the ```unknownFutureValue``` value is handled as part of a `` | ```enumProperty gt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value greater than ```newValue``` | | ```enumProperty lt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value less than ```newValue``` | -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 ```Prefer: include-unknown-enum-members``` header is absent. +- 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 ```Prefer: include-unknown-enum-members``` header is absent. ## Examples @@ -310,3 +312,179 @@ Preference-Applied: include-unknown-enum-members "processorArchitecture": "quantum" } ``` + +## Flag Enum Examples + +For the following examples we will consider the ```windowsUniversalAppX``` entity which refers to the ```windowsArchitecture``` flag enum type. + +```xml + + + + + +``` + +When the ```windowsArchitecture``` enum was initially published to Graph it was defined as below: + +```xml + + + + + + + + + +``` + +The enum was later extended to add a new value of ```quantum``` leading to the below CSDL + +```xml + + + + + + + + + +``` + +### Flag Enum Default Behavior + +```http +GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures +``` + +```json +{ + "value": [ + { + "id": "0", + "displayName": "OneNote", + "applicableArchitectures" : "neutral" + }, + { + "id": "1", + "displayName": "Minecraft", + "applicableArchitectures": "x86,x64,arm,unknownFutureValue" + } + { + "id": "2", + "displayName": "Edge", + "applicableArchitectures": "x64,arm,unknownFutureValue" + } + ] +} +``` + +In this case the value of the ```applicableArchitectures``` property includes ```quantum``` however since the client did not request the ```include-unknown-enum-members``` header the value was replaced with ```unknownFutureValue``` + +### Flag Enum Include opt-in Header + +```http +GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures + +Prefer: include-unknown-enum-members +``` + +```json +Preference-Applied: include-unknown-enum-members + +{ + "value": [ + { + "id": "0", + "displayName": "OneNote", + "applicableArchitectures" : "neutral" + }, + { + "id": "1", + "displayName": "Minecraft", + "applicableArchitectures": "x86,x64,arm,quantum" + } + { + "id": "2", + "displayName": "Edge", + "applicableArchitectures": "x64,arm,quantum" + } + ] +} +``` + +### Flag Enum Default Filter Behavior + +```http +GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures&$filter=applicableArchitectures has unknownFutureValue +``` + +```json +{ + "value": [ + { + "id": "1", + "displayName": "Minecraft", + "applicableArchitectures": "x86,x64,arm,unknownFutureValue" + } + { + "id": "2", + "displayName": "Edge", + "applicableArchitectures": "x64,arm,unknownFutureValue" + } + ] +} +``` + +### Flag Enum Include opt-in Header Filter Behavior + +```http +GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures&$filter=applicableArchitectures has unknownFutureValue + +Prefer: include-unknown-enum-members +``` + +```json +Preference-Applied: include-unknown-enum-members + +{ + "value": [] +} +``` + +### Flag Enum Patch Example + +```http +PATCH https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps/1 + +{ + "displayName": "Minecraft 2", + "processorArchitecture": "unknownFutureValue" +} +``` + +```json +{ + "id": "1", + "displayName": "Minecraft 2", + "applicableArchitectures": "unknownFutureValue" +} +``` + +```http +GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps/1 + +Prefer: include-unknown-enum-members +``` + +```json +Preference-Applied: include-unknown-enum-members + +{ + "id": "1", + "displayName": "Minecraft 2", + "applicableArchitectures": "x86,x64,arm,quantum" +} +``` From 2585cbef9a7150d0c4e1c2e73ad6337e3188247d Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 6 Apr 2022 19:00:15 -0400 Subject: [PATCH 324/729] Update graph/patterns/namespace.md Co-authored-by: Darrel --- graph/patterns/namespace.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 44242d95..47c06b7c 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -33,7 +33,7 @@ name. For consistent user experience namespaces MUST be aligned with the corresp ## When to Use this Pattern API resource grouping creates a user-friendly experience keeping all resources -for a specific feature close together. It also allows generating smallersmaller downloadable client components +for a specific feature close together and limits the length of IDE prompts such as auto-complete in some programming languages. We recommend creating a new namespace when a new top-level API category is From 5b9c4b7e13d8844f491f26a140cb7797b4c5ffec Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 6 Apr 2022 19:00:37 -0400 Subject: [PATCH 325/729] Update graph/patterns/namespace.md Co-authored-by: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> --- graph/patterns/namespace.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 47c06b7c..71925a90 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -46,7 +46,7 @@ introduced. Graph type names must be descriptive and unique within the API surface without requiring full qualification. -2. Namespace must be consistent with API category in the navigation path according to [Microsoft Graph REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/graph/GuidelinesGraph.md#uniform-resource-locators-urls) +2. Namespace must be consistent with API category in the navigation path according to [Microsoft Graph REST API Guidelines](GuidelinesGraph.md#uniform-resource-locators-urls) 3. When type name is ambiguous and requires a namespace qualifier, changing namespace is a breaking change. From 22fdf412c2d6a93f9159adc6e7ac5fe064887b76 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 6 Apr 2022 19:02:02 -0400 Subject: [PATCH 326/729] Update graph/patterns/namespace.md Co-authored-by: Darrel --- graph/patterns/namespace.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 71925a90..545211f5 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -58,7 +58,7 @@ introduced. fully qualified name (namespace + type name). 6. Cyclical references between namespaces are not allowed as many - object-oriented languages don’t support a cycle in inheritance. + object-oriented languages don’t support a cycles between namespaces. 7. Microsoft Graph has heuristic rules for declared namespaces: From c44093363eedc35e38ab8e282d628997bdc1c8d8 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Fri, 8 Apr 2022 11:08:49 -0700 Subject: [PATCH 327/729] Resolve PR and offline feedback --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 78a80e61..45161881 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -377,7 +377,7 @@ Services, and the clients that access them, may be written in multiple languages :white_check_mark: **DO** use a fixed time interval to express durations e.g., milliseconds, seconds, minutes, days, etc., and include the time unit in the property name e.g., `backupTimeInMinutes` or `ttlSeconds`. -:heavy_check_mark: **YOU MAY** use [RFC3339 durations](https://wikipedia.org/wiki/ISO_8601#Durations) only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P3M" represents 3 months no matter how many days between the start and end, or "P1Y" represents 366 days on a leap year. +:heavy_check_mark: **YOU MAY** use [RFC3339 time intervals](https://wikipedia.org/wiki/ISO_8601#Durations) only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P3M" represents 3 months no matter how many days between the start and end dates, or "P1Y" represents 366 days on a leap year. The value must be round-trippable. :white_check_mark: **DO** use [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) for UUIDs. From 3c7416cadfe18e5fb43139fb412b6f749036962f Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 12 Apr 2022 11:22:10 -0400 Subject: [PATCH 328/729] Update README.md Minor changes to readme.md --- azure/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/README.md b/azure/README.md index ba88203c..e92f81a4 100644 --- a/azure/README.md +++ b/azure/README.md @@ -5,7 +5,7 @@ Designing powerful APIs with strong defaults, consistent behavior across related * [Considerations for Service Design](ConsiderationsForServiceDesign.md) * [REST API Guidelines](Guidelines.md) * [OpenAPI Style Guidelines](https://github.com/Azure/azure-api-style-guide/blob/main/README.md) -* [Breaking Changes](http://aka.ms/AzBreakingChangesPolicy/) +* [Breaking Changes](http://aka.ms/AzBreakingChangesPolicy/) Note: Internal Microsoft link You can reach out to use via [email](mailto://azureapirbcore@microsoft.com) or in our [Teams](https://teams.microsoft.com/l/team/19%3a3ebb18fded0e47938f998e196a52952f%40thread.tacv2/conversations?groupId=1a10b50c-e870-4fe0-8483-bf5542a8d2d8&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) channel. From c6983c756eeb82d241f140686ca9a92033f32249 Mon Sep 17 00:00:00 2001 From: Shawn Gaul Date: Tue, 12 Apr 2022 11:53:43 -0700 Subject: [PATCH 329/729] Add additional guidance for ETag computation --- azure/Guidelines.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a99869db..0fb54e99 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -988,12 +988,20 @@ When supporting optimistic concurrency: #### Computing ETags The strategy that you use to compute the `ETag` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the `ETag`. Another common strategy for determining the value of an `ETag` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. +:ballot_box_with_check: **YOU SHOULD** use a hash of the representation of a resource rather than a last modified/version number + +> While it may be tempting to use a revision/version number for the resource as the ETag, it interferes with client's ability to retry update requests. If a client sends a conditional update request, the service acts on the request, but the client never receives a response, a subsequent identical update will be seen as a conflict even though the retried request is attempting to make the same update. + :ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. +:ballot_box_with_check: **YOU SHOULD**, if supporting range requests, use a strong ETag in order to support caching. + :heavy_check_mark: **YOU MAY** use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. :heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. +:white_check_box: **DO**, when supporting multiple representations (e.g. Content-Encodings) for the same resource, generate different ETag values for the different representations. + ### Distributed Tracing & Telemetry Azure SDK client guidelines specify that client libraries must send telemetry data through the `User-Agent` header, `X-MS-UserAgent` header, and Open Telemetry. Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. From 1e98d6525d09e58e3d12cd9b614bfffd409b6f3e Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 15 Apr 2022 17:15:00 -0400 Subject: [PATCH 330/729] Resolving comments and typos --- graph/patterns/namespace.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 545211f5..9a2c4105 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -22,7 +22,7 @@ namespace and logically organize related API entities in the Graph metadata. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A public namespace must have "microsoft.graph.” prefix and be presented in camel +A public namespace must have "microsoft.graph." prefix and be presented in camel case, i.e microsoft.graph.myNamespace. When type casting is required in the API query, request or response, a fully @@ -36,8 +36,7 @@ API resource grouping creates a user-friendly experience keeping all resources for a specific feature close together and limits the length of IDE prompts such as auto-complete in some programming languages. -We recommend creating a new namespace when a new top-level API category is -introduced. +We recommend that a new namespace should be aligned with top-level API category. ## Issues and Considerations @@ -60,12 +59,14 @@ introduced. 6. Cyclical references between namespaces are not allowed as many object-oriented languages don’t support a cycles between namespaces. -7. Microsoft Graph has heuristic rules for declared namespaces: +7. Microsoft Graph has some predefined constraints for declared namespaces: - 1. All public namespaces must have a prefix ‘microsoft.graph’ + 1. All public namespaces must have a prefix "microsoft.graph" - 2. If a namespace does not begin with ‘microsoft.graph’ prefix, all types - in the schema will be coerced into the main ‘microsoft.graph’ namespace. + 2. Only one level of nesting deeper then "microsoft.graph" is supported + + 3. If a namespace does not begin with "microsoft.graph" prefix, all types + in the schema will be coerced into the main "microsoft.graph" namespace. ## Examples @@ -80,27 +81,26 @@ introduced. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Fully qualified type name: microsoft.graph.search.bookmark +Fully qualified type name: "microsoft.graph.search.bookmark" ### Managing multiple schemas: Workloads must define schemas in their csdl using the Edmx format. -[Microsoft.IC3.DataPlatform](https://dev.azure.com/msazure/One/_git/AD-AggregatorService-Workloads?path=%2FWorkloads%2FMicrosoft.IC3.DataPlatform%2Foverride%2Fschema-Prod-beta.csdl) -is an example of a workload that exposes multiple namespaces. +Here is an example of a workload that exposes multiple namespaces. **Tip:** As with schemas that exist in the microsoft.graph namespace, defining an -entity type is optional, AGS will transform your schema to make all entity types -derive from microsoft.graph.entity. +entity type is optional, by default your schema derives all entity types +from microsoft.graph.entity. **Warning:** Do not deviate from the general structure in the example below. Schema validation tool expects the XML structure (including xml namespace declarations) to match the example below. ```XML - + - - + + From 35d1c1da452ba6560a9b6bbee5d91cb3586ab4a7 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 15 Apr 2022 17:17:08 -0400 Subject: [PATCH 331/729] fixing bullets --- graph/patterns/namespace.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 9a2c4105..758a1e37 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -61,11 +61,11 @@ We recommend that a new namespace should be aligned with top-level API category. 7. Microsoft Graph has some predefined constraints for declared namespaces: - 1. All public namespaces must have a prefix "microsoft.graph" + - All public namespaces must have a prefix "microsoft.graph" - 2. Only one level of nesting deeper then "microsoft.graph" is supported + - Only one level of nesting deeper then "microsoft.graph" is supported - 3. If a namespace does not begin with "microsoft.graph" prefix, all types + - If a namespace does not begin with "microsoft.graph" prefix, all types in the schema will be coerced into the main "microsoft.graph" namespace. ## Examples From b694497cb8f9c34b31848c0024d15a544d98853d Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 15 Apr 2022 17:44:33 -0400 Subject: [PATCH 332/729] updated table of patterns --- graph/GuidelinesGraph.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index bc4a7006..2ca83c75 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -414,8 +414,11 @@ Recommended API Design patterns: | Type Hierarchy | The ability to model is-a relationships using subtypes. | [Subtypes](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | | Facets | The ability to model parent-child relationships using Facet pattern. | [Facets](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | | Dictionary | The ability for clients to provide an unknown quantity of data elements of the same type. | [Dictionary](./patterns/dictionary.md) | +| Evolvable Enums | The ability to extend enumerated types without breaking changes. | [Evolvable Enums](./patterns/evolvable-enums.md) | +| Change Tracking | The ability to keep API consumers in sync with changes without polling. | [Change Tracking](./patterns/change-tracking.md) | +| Namespace | The ability to organize resource definitions into a logical set. | [Namespace](./patterns/namespace.md) | - | + | ## References From d5690242d599ddf3bd9c469476feb77269cc9eb2 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 15 Apr 2022 17:52:42 -0400 Subject: [PATCH 333/729] formatting --- graph/patterns/namespace.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 758a1e37..aad80e78 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -2,10 +2,11 @@ Microsoft Graph API Design Pattern -### *The Namespace provides the ability to organize resource definitions together into a logical set.* +*The Namespace provides the ability to organize resource definitions together into a logical set.* ## Problem +--- When building a complex offering API designers may need to model many different resources and their relationships. For better user experience and discoverability related API elements need to be grouped together. @@ -13,6 +14,7 @@ discoverability related API elements need to be grouped together. ## Solution +--- API designers can use the Namespace attribute of the CSDL schema to declare a namespace and logically organize related API entities in the Graph metadata. @@ -32,6 +34,7 @@ name. For consistent user experience namespaces MUST be aligned with the corresp ## When to Use this Pattern +--- API resource grouping creates a user-friendly experience keeping all resources for a specific feature close together and limits the length of IDE prompts such as auto-complete in some programming languages. @@ -40,6 +43,7 @@ We recommend that a new namespace should be aligned with top-level API category. ## Issues and Considerations +--- 1. Microsoft Graph consistency requirements discourage using the same type names for different concepts even within different namespaces. Microsoft Graph type names must be descriptive and unique within the API @@ -70,6 +74,7 @@ We recommend that a new namespace should be aligned with top-level API category. ## Examples +--- ### Namespace and type declarations: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ XML From d36dc70fa8e14817d283c969543afc3a03bfd2de Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Tue, 26 Apr 2022 15:51:07 -0600 Subject: [PATCH 334/729] add alternate key --- graph/patterns/alternate-key.md | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 graph/patterns/alternate-key.md diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md new file mode 100644 index 00000000..4373e553 --- /dev/null +++ b/graph/patterns/alternate-key.md @@ -0,0 +1,48 @@ +# Alternate Key Pattern + +Microsoft Graph API Design Pattern + +*The Alternate Key Pattern provides the ability to query for a single, specific entity identifiable through an alternative attribute (called key) that is not its unique identifier* + +## Problem +-------- + +All entities in our system are identified by an UUID - which guarantees uniqueness. Often though, that same entity can also be uniquely identified by an alternative, more convenient attribute. + +Take a look at the `user` entity: while the UUID remains a perfectly valid way to get the entity details, the `email` address is also an unique attribute that could be used to identify it. + +While it is still possible to use the oData filter, such as + +`serviceRoot/Users?$filter=email eq 'hello@microsoft.com'`, the returned result is wrapped in an array that needs to be unpacked. + + +## Solution +-------- + +oData offers entity addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. + +http://host/service/Employees(0) - Retrieves the employee with ID = 0 +http://host/service/Employees(email='hello@microsoft.com') Retrieves the employee with the email matching `hello@microsoft.com` + +## When to Use this Pattern +------------------------ + +This pattern works when the alternate key is good enough to identify a single entity that is properly namespaced; while it does not work if the resultset has more than one element. + +In such case, we **strongly** advice to throw an exception and encourage the user to use `$filter` rather than returning the first result of the query + +## Example +------- + +The same user identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with specified key property name, and the canonical short form without key property name + +http://host/service/Employees/1a89ade6-9f59-4fea-a139-23f84e3aef66 + +http://host/service/Employees(ssn='123-45-6789') + +http://host/service/Employees(email='hello@microsoft.com') + + + + + From 9f54298203e5b7d626ff2294553ac5dc67358c6d Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Mon, 2 May 2022 09:33:54 -0500 Subject: [PATCH 335/729] Prefer real Graph URL --- graph/patterns/alternate-key.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 4373e553..6ff213d8 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -21,8 +21,8 @@ While it is still possible to use the oData filter, such as oData offers entity addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. -http://host/service/Employees(0) - Retrieves the employee with ID = 0 -http://host/service/Employees(email='hello@microsoft.com') Retrieves the employee with the email matching `hello@microsoft.com` +https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0 +https://graph.microsoft.com/v1.0/users(email='hello@microsoft.com') Retrieves the employee with the email matching `hello@microsoft.com` ## When to Use this Pattern ------------------------ @@ -36,11 +36,11 @@ In such case, we **strongly** advice to throw an exception and encourage the use The same user identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with specified key property name, and the canonical short form without key property name -http://host/service/Employees/1a89ade6-9f59-4fea-a139-23f84e3aef66 +https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 -http://host/service/Employees(ssn='123-45-6789') +https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') -http://host/service/Employees(email='hello@microsoft.com') +https://graph.microsoft.com/v1.0/users(email='hello@microsoft.com') From 18fb7dc91a2e19cfff5d135fe218252312141d22 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Mon, 2 May 2022 09:35:45 -0500 Subject: [PATCH 336/729] use contoso as domain --- graph/patterns/alternate-key.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 6ff213d8..08bd93ea 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -13,7 +13,7 @@ Take a look at the `user` entity: while the UUID remains a perfectly valid way t While it is still possible to use the oData filter, such as -`serviceRoot/Users?$filter=email eq 'hello@microsoft.com'`, the returned result is wrapped in an array that needs to be unpacked. +`https://graph.microsoft.com/v1.0/users?filter=mail eq 'bob@contoso.com'`, the returned result is wrapped in an array that needs to be unpacked. ## Solution @@ -22,7 +22,7 @@ While it is still possible to use the oData filter, such as oData offers entity addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0 -https://graph.microsoft.com/v1.0/users(email='hello@microsoft.com') Retrieves the employee with the email matching `hello@microsoft.com` +https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com` ## When to Use this Pattern ------------------------ @@ -40,7 +40,7 @@ https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') -https://graph.microsoft.com/v1.0/users(email='hello@microsoft.com') +https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') From 8f4966c20daf27bec6c5706270879510d4b70cb5 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Mon, 2 May 2022 09:48:14 -0500 Subject: [PATCH 337/729] Reword the when to use --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 08bd93ea..f1c87833 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -27,7 +27,7 @@ https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the em ## When to Use this Pattern ------------------------ -This pattern works when the alternate key is good enough to identify a single entity that is properly namespaced; while it does not work if the resultset has more than one element. +This pattern works and makes sense when the alternate key is good enough to identify a single entity and provides an useful alternative to the client; while it does not work if the resultset has more than one element. In such case, we **strongly** advice to throw an exception and encourage the user to use `$filter` rather than returning the first result of the query From 4f5e784a88acd316223470ec05b756a66ec66d77 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Mon, 2 May 2022 09:48:48 -0500 Subject: [PATCH 338/729] email -> mail --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index f1c87833..fcfc1a8c 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -9,7 +9,7 @@ Microsoft Graph API Design Pattern All entities in our system are identified by an UUID - which guarantees uniqueness. Often though, that same entity can also be uniquely identified by an alternative, more convenient attribute. -Take a look at the `user` entity: while the UUID remains a perfectly valid way to get the entity details, the `email` address is also an unique attribute that could be used to identify it. +Take a look at the `user` entity: while the UUID remains a perfectly valid way to get the entity details, the `mail` address is also an unique attribute that could be used to identify it. While it is still possible to use the oData filter, such as From 85d633a0f83a2fbe0f3711865cdc368e64fb4293 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Mon, 2 May 2022 09:49:15 -0500 Subject: [PATCH 339/729] uuid->id --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index fcfc1a8c..d55e15b7 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -9,7 +9,7 @@ Microsoft Graph API Design Pattern All entities in our system are identified by an UUID - which guarantees uniqueness. Often though, that same entity can also be uniquely identified by an alternative, more convenient attribute. -Take a look at the `user` entity: while the UUID remains a perfectly valid way to get the entity details, the `mail` address is also an unique attribute that could be used to identify it. +Take a look at the `user` entity: while the `id` remains a perfectly valid way to get the entity details, the `mail` address is also an unique attribute that could be used to identify it. While it is still possible to use the oData filter, such as From ec0c55c04eff9b4d9eb7e0412c4c462bd9cf2846 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Mon, 2 May 2022 09:50:40 -0500 Subject: [PATCH 340/729] expand uuid --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index d55e15b7..ea013d37 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -7,7 +7,7 @@ Microsoft Graph API Design Pattern ## Problem -------- -All entities in our system are identified by an UUID - which guarantees uniqueness. Often though, that same entity can also be uniquely identified by an alternative, more convenient attribute. +All entities in our system are identified by an [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) - which guarantees uniqueness. Often though, that same entity can also be uniquely identified by an alternative, more convenient attribute. Take a look at the `user` entity: while the `id` remains a perfectly valid way to get the entity details, the `mail` address is also an unique attribute that could be used to identify it. From a3966c776a1fb807d5be9e9eca821a983c8a8541 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Mon, 2 May 2022 09:53:58 -0500 Subject: [PATCH 341/729] entity->resource --- graph/patterns/alternate-key.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index ea013d37..cde9d8b2 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -2,14 +2,14 @@ Microsoft Graph API Design Pattern -*The Alternate Key Pattern provides the ability to query for a single, specific entity identifiable through an alternative attribute (called key) that is not its unique identifier* +*The Alternate Key Pattern provides the ability to query for a single, specific resource identifiable through an alternative attribute (called key) that is not its unique identifier* ## Problem -------- -All entities in our system are identified by an [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) - which guarantees uniqueness. Often though, that same entity can also be uniquely identified by an alternative, more convenient attribute. +All entities in our system are identified by an [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) - which guarantees uniqueness. Often though, that same resource can also be uniquely identified by an alternative, more convenient attribute. -Take a look at the `user` entity: while the `id` remains a perfectly valid way to get the entity details, the `mail` address is also an unique attribute that could be used to identify it. +Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also an unique attribute that could be used to identify it. While it is still possible to use the oData filter, such as @@ -19,7 +19,7 @@ While it is still possible to use the oData filter, such as ## Solution -------- -oData offers entity addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. +oData offers resource addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0 https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com` @@ -27,7 +27,7 @@ https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the em ## When to Use this Pattern ------------------------ -This pattern works and makes sense when the alternate key is good enough to identify a single entity and provides an useful alternative to the client; while it does not work if the resultset has more than one element. +This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides an useful alternative to the client; while it does not work if the resultset has more than one element. In such case, we **strongly** advice to throw an exception and encourage the user to use `$filter` rather than returning the first result of the query From 37ac246e92808c13f697088bb23b512f9224382b Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Mon, 2 May 2022 09:58:26 -0500 Subject: [PATCH 342/729] property->attribute --- graph/patterns/alternate-key.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index cde9d8b2..0e723b8d 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -19,7 +19,7 @@ While it is still possible to use the oData filter, such as ## Solution -------- -oData offers resource addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. +oData offers resource addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key attribute name to unambiguously determine the alternate key. https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0 https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com` @@ -34,7 +34,7 @@ In such case, we **strongly** advice to throw an exception and encourage the use ## Example ------- -The same user identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with specified key property name, and the canonical short form without key property name +The same user identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with specified key attribute name, and the canonical short form without key attribute name https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 From 680f5fe2a16b3409501f3b0c05d254645d6761ef Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Mon, 2 May 2022 10:06:24 -0500 Subject: [PATCH 343/729] complete sentence --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 0e723b8d..d6e133ba 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -7,7 +7,7 @@ Microsoft Graph API Design Pattern ## Problem -------- -All entities in our system are identified by an [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) - which guarantees uniqueness. Often though, that same resource can also be uniquely identified by an alternative, more convenient attribute. +All entities in our system are identified by an [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) - which guarantees uniqueness. Often though, that same resource can also be uniquely identified by an alternative, more convenient attribute that provides a better developer experience. Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also an unique attribute that could be used to identify it. From 252371b71cc5489446b3c36eecfc9073d3b602e6 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Mon, 2 May 2022 11:07:40 -0500 Subject: [PATCH 344/729] markdown format --- graph/patterns/alternate-key.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index d6e133ba..b331c4cc 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -2,10 +2,11 @@ Microsoft Graph API Design Pattern -*The Alternate Key Pattern provides the ability to query for a single, specific resource identifiable through an alternative attribute (called key) that is not its unique identifier* +_The Alternate Key Pattern provides the ability to query for a single, specific resource identifiable through an alternative attribute (called key) that is not its unique identifier_ ## Problem --------- + +--- All entities in our system are identified by an [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) - which guarantees uniqueness. Often though, that same resource can also be uniquely identified by an alternative, more convenient attribute that provides a better developer experience. @@ -15,9 +16,9 @@ While it is still possible to use the oData filter, such as `https://graph.microsoft.com/v1.0/users?filter=mail eq 'bob@contoso.com'`, the returned result is wrapped in an array that needs to be unpacked. - ## Solution --------- + +--- oData offers resource addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key attribute name to unambiguously determine the alternate key. @@ -25,14 +26,16 @@ https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0 https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com` ## When to Use this Pattern ------------------------- + +--- This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides an useful alternative to the client; while it does not work if the resultset has more than one element. -In such case, we **strongly** advice to throw an exception and encourage the user to use `$filter` rather than returning the first result of the query +In such case, we **strongly** advice to throw an exception and encourage the user to use `$filter` rather than returning the first result of the query ## Example -------- + +--- The same user identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with specified key attribute name, and the canonical short form without key attribute name @@ -41,8 +44,3 @@ https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') - - - - - From 67610b29fcdb3599db229e5e1374d56b963351ec Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Tue, 3 May 2022 08:03:39 -0500 Subject: [PATCH 345/729] change wording --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index b331c4cc..6d2a36b5 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -31,7 +31,7 @@ https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the em This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides an useful alternative to the client; while it does not work if the resultset has more than one element. -In such case, we **strongly** advice to throw an exception and encourage the user to use `$filter` rather than returning the first result of the query +In such case, the system SHOULD throw an exception and encourage the user to use `$filter` rather than returning the first result of the query ## Example From c2699db8a6154189f49bf320e59c5866b6a6d043 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Tue, 3 May 2022 08:13:32 -0500 Subject: [PATCH 346/729] add $ sign --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 6d2a36b5..b0f64dee 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -14,7 +14,7 @@ Take a look at the `user` resource: while the `id` remains a perfectly valid way While it is still possible to use the oData filter, such as -`https://graph.microsoft.com/v1.0/users?filter=mail eq 'bob@contoso.com'`, the returned result is wrapped in an array that needs to be unpacked. +`https://graph.microsoft.com/v1.0/users?$filter=mail eq 'bob@contoso.com'`, the returned result is wrapped in an array that needs to be unpacked. ## Solution From 155a3f9b682931282ae98ff570e0db916c433b71 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Tue, 3 May 2022 08:18:13 -0500 Subject: [PATCH 347/729] add example response --- graph/patterns/alternate-key.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index b0f64dee..eff536b4 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -44,3 +44,21 @@ https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') + +All of the 3 will yield the sare response: + + +```json +{ + "givenName": "Bob", + "jobTitle": "Retail Manager", + "mail": "bob@contoso.com", + "mobilePhone": "+1 425 555 0109", + "officeLocation": "18/2111", + "preferredLanguage": "en-US", + "surname": "Vance", + "userPrincipalName": "bob@contoso.com", + "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" +} +``` + From 81ca72a3a9b87959c4128f955ce756804dd7e5d9 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Tue, 3 May 2022 08:20:55 -0500 Subject: [PATCH 348/729] clarify uniqueness --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index eff536b4..8809111d 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -8,7 +8,7 @@ _The Alternate Key Pattern provides the ability to query for a single, specific --- -All entities in our system are identified by an [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) - which guarantees uniqueness. Often though, that same resource can also be uniquely identified by an alternative, more convenient attribute that provides a better developer experience. +The resources exposed in Graph are identified by an [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) - which guarantees uniqueness inside the same resource type. Often though, that same resource can also be uniquely identified by an alternative, more convenient attribute that provides a better developer experience. Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also an unique attribute that could be used to identify it. From e50eb1136def83180a9a68dbd264b1d0c8b24895 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Tue, 3 May 2022 08:25:18 -0500 Subject: [PATCH 349/729] attribute->property --- graph/patterns/alternate-key.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 8809111d..874033da 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -2,15 +2,15 @@ Microsoft Graph API Design Pattern -_The Alternate Key Pattern provides the ability to query for a single, specific resource identifiable through an alternative attribute (called key) that is not its unique identifier_ +_The Alternate Key Pattern provides the ability to query for a single, specific resource identifiable through an alternative property (called key) that is not its unique identifier_ ## Problem --- -The resources exposed in Graph are identified by an [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) - which guarantees uniqueness inside the same resource type. Often though, that same resource can also be uniquely identified by an alternative, more convenient attribute that provides a better developer experience. +The resources exposed in Graph are identified by an [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) - which guarantees uniqueness inside the same resource type. Often though, that same resource can also be uniquely identified by an alternative, more convenient property that provides a better developer experience. -Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also an unique attribute that could be used to identify it. +Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also an unique property that could be used to identify it. While it is still possible to use the oData filter, such as @@ -20,7 +20,7 @@ While it is still possible to use the oData filter, such as --- -oData offers resource addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key attribute name to unambiguously determine the alternate key. +oData offers resource addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0 https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com` @@ -37,7 +37,7 @@ In such case, the system SHOULD throw an exception and encourage the user to use --- -The same user identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with specified key attribute name, and the canonical short form without key attribute name +The same user identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with specified key property name, and the canonical short form without key property name https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 From 931526c5429df5a2d1bc212ddfca3ec58e43e8ae Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Tue, 3 May 2022 11:25:51 -0500 Subject: [PATCH 350/729] add example --- graph/patterns/alternate-key.md | 52 ++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 874033da..9e82c2ca 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -39,26 +39,50 @@ In such case, the system SHOULD throw an exception and encourage the user to use The same user identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with specified key property name, and the canonical short form without key property name -https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 +1. Get a specific resource through `$filter`: -https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') +```http +GET https://graph.microsoft.com/v1.0/users/?$filter=(ssn eq '123-45-6789') +``` + +```json +{ + "value": [ + { + "givenName": "Bob", + "jobTitle": "Retail Manager", + "mail": "bob@contoso.com", + "mobilePhone": "+1 425 555 0109", + "officeLocation": "18/2111", + "preferredLanguage": "en-US", + "surname": "Vance", + "userPrincipalName": "bob@contoso.com", + "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" + } + ] +} +``` -https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') +2. Get a specific resource through the alternate key: -All of the 3 will yield the sare response: +```http +GET https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 +GET https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') +GET https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') +``` +All of the 3 will yield the sare response: ```json { - "givenName": "Bob", - "jobTitle": "Retail Manager", - "mail": "bob@contoso.com", - "mobilePhone": "+1 425 555 0109", - "officeLocation": "18/2111", - "preferredLanguage": "en-US", - "surname": "Vance", - "userPrincipalName": "bob@contoso.com", - "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" + "givenName": "Bob", + "jobTitle": "Retail Manager", + "mail": "bob@contoso.com", + "mobilePhone": "+1 425 555 0109", + "officeLocation": "18/2111", + "preferredLanguage": "en-US", + "surname": "Vance", + "userPrincipalName": "bob@contoso.com", + "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" } ``` - From 74d17f8b14b0674e8e10d6a67abc7de0e4dbe4ef Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Tue, 3 May 2022 14:43:52 -0500 Subject: [PATCH 351/729] add error example and clarify the status code to return --- graph/patterns/alternate-key.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 9e82c2ca..7b94df60 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -31,7 +31,10 @@ https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the em This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides an useful alternative to the client; while it does not work if the resultset has more than one element. -In such case, the system SHOULD throw an exception and encourage the user to use `$filter` rather than returning the first result of the query +In such case, the workload has two choices: + +1. If the workload defines a list of alternate keys for a resource and the requested key is not there, the workload SHOULD return `422` +2. If the workload does not have a list of alternate keys, it can still query the data source, but if the result yields more than a record, it SHOULD return `422` and encourage the user to use `$filter` rather than returning the first result of the query ## Example @@ -86,3 +89,11 @@ All of the 3 will yield the sare response: "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" } ``` + +3. Requesting a resource through an alternate key which yields more than a result + +```http +GET https://graph.microsoft.com/v1.0/users(name='Bob') + +422 Precondition Failed +``` From 97b3a275b348dcf1bb78c40ef133b4b119ddb24e Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Wed, 4 May 2022 15:37:58 -0500 Subject: [PATCH 352/729] Update graph/patterns/alternate-key.md Co-authored-by: Darrel --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 7b94df60..d0b21a8d 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -12,7 +12,7 @@ The resources exposed in Graph are identified by an [UUID (Universally Unique Id Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also an unique property that could be used to identify it. -While it is still possible to use the oData filter, such as +While it is still possible to use the OData filter, such as `https://graph.microsoft.com/v1.0/users?$filter=mail eq 'bob@contoso.com'`, the returned result is wrapped in an array that needs to be unpacked. From 6255298533cf4e6f73b4489e1e00c594c8bbd957 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Wed, 4 May 2022 15:38:25 -0500 Subject: [PATCH 353/729] Update graph/patterns/alternate-key.md Co-authored-by: Darrel --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index d0b21a8d..2290361c 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -20,7 +20,7 @@ While it is still possible to use the OData filter, such as --- -oData offers resource addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. +OData offers resource addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0 https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com` From 590da3e21165f617d4f7d28f7a4bd49c715408f9 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Wed, 4 May 2022 15:41:44 -0500 Subject: [PATCH 354/729] clarify sample not working --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 2290361c..6e877004 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -20,7 +20,7 @@ While it is still possible to use the OData filter, such as --- -OData offers resource addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. +OData offers resource addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. (Note: this is a hypothetical sample) https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0 https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com` From d93bea3cb5e7cbf7503b2bdd6b0ce64d1f33ec3b Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Wed, 4 May 2022 15:45:11 -0500 Subject: [PATCH 355/729] convert error to 400 --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 6e877004..488ce8eb 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -95,5 +95,5 @@ All of the 3 will yield the sare response: ```http GET https://graph.microsoft.com/v1.0/users(name='Bob') -422 Precondition Failed +400 Bad Request ``` From 7250de3f2553294ca8479381ddf88e7eec19536e Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Wed, 4 May 2022 15:52:25 -0500 Subject: [PATCH 356/729] add csdl example --- graph/patterns/alternate-key.md | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 488ce8eb..b1193a01 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -42,6 +42,42 @@ In such case, the workload has two choices: The same user identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with specified key property name, and the canonical short form without key property name +Declare `mail` and `ssn` as alternate keys on an entity: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + 1. Get a specific resource through `$filter`: ```http From ba51283965faa3c744053e2d2f0730acdc19913d Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Wed, 4 May 2022 15:59:30 -0500 Subject: [PATCH 357/729] clarify the example ' --- graph/patterns/alternate-key.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index b1193a01..7d5cc041 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -50,7 +50,7 @@ Declare `mail` and `ssn` as alternate keys on an entity: - + @@ -102,7 +102,7 @@ GET https://graph.microsoft.com/v1.0/users/?$filter=(ssn eq '123-45-6789') } ``` -2. Get a specific resource through the alternate key: +2. Get a specific resource through the unique identifier, and then through the two alternate keys: ```http GET https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 From 1f3d11ebe3fde8eb64a95665c5935bcaaeca42e3 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Wed, 4 May 2022 16:02:58 -0500 Subject: [PATCH 358/729] use primary key --- graph/patterns/alternate-key.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 7d5cc041..3f76cbb3 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -2,13 +2,13 @@ Microsoft Graph API Design Pattern -_The Alternate Key Pattern provides the ability to query for a single, specific resource identifiable through an alternative property (called key) that is not its unique identifier_ +_The Alternate Key Pattern provides the ability to query for a single, specific resource identifiable through an alternative property (called key) that is not its primary key_ ## Problem --- -The resources exposed in Graph are identified by an [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) - which guarantees uniqueness inside the same resource type. Often though, that same resource can also be uniquely identified by an alternative, more convenient property that provides a better developer experience. +The resources exposed in Graph are identified through a Primary Key - which guarantees uniqueness inside the same resource type. Often though, that same resource can also be uniquely identified by an alternative, more convenient property (or set of properties) that provides a better developer experience. Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also an unique property that could be used to identify it. @@ -102,7 +102,7 @@ GET https://graph.microsoft.com/v1.0/users/?$filter=(ssn eq '123-45-6789') } ``` -2. Get a specific resource through the unique identifier, and then through the two alternate keys: +2. Get a specific resource through its primary key, and then through the two alternate keys: ```http GET https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 From d46daa8057ff1160e00b9e4186e1801bc50b7992 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Wed, 4 May 2022 16:04:45 -0500 Subject: [PATCH 359/729] minor rewordings --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 3f76cbb3..5b5f4e32 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -2,7 +2,7 @@ Microsoft Graph API Design Pattern -_The Alternate Key Pattern provides the ability to query for a single, specific resource identifiable through an alternative property (called key) that is not its primary key_ +_The Alternate Key Pattern provides the ability to query for a single, specific resource identifiable through an alternative set of properties that is not its primary key_ ## Problem From a062deaf52d5a0c8d3725dfab3629b228f39e2f7 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Wed, 4 May 2022 17:15:23 -0500 Subject: [PATCH 360/729] Update graph/patterns/alternate-key.md Co-authored-by: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 5b5f4e32..6e9bac4b 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -126,7 +126,7 @@ All of the 3 will yield the sare response: } ``` -3. Requesting a resource through an alternate key which yields more than a result +3. Requesting a resource through an alternate key which yields more than one result ```http GET https://graph.microsoft.com/v1.0/users(name='Bob') From 2efb1af44e13f21a34bb8b39f1ed3b537e578111 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Wed, 4 May 2022 17:18:11 -0500 Subject: [PATCH 361/729] remove arbitrary querying --- graph/patterns/alternate-key.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 6e9bac4b..250eb2e3 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -29,12 +29,7 @@ https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the em --- -This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides an useful alternative to the client; while it does not work if the resultset has more than one element. - -In such case, the workload has two choices: - -1. If the workload defines a list of alternate keys for a resource and the requested key is not there, the workload SHOULD return `422` -2. If the workload does not have a list of alternate keys, it can still query the data source, but if the result yields more than a record, it SHOULD return `422` and encourage the user to use `$filter` rather than returning the first result of the query +This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides an useful alternative to the client; while it does not work if the resultset has more than one element. In such case, the workload SHOULD return `400` ## Example From ffe5f173b203f2e8e2bde0363b11cb3109e6f3a3 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Thu, 5 May 2022 09:21:14 -0500 Subject: [PATCH 362/729] change example 3 --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 250eb2e3..d29e821a 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -121,7 +121,7 @@ All of the 3 will yield the sare response: } ``` -3. Requesting a resource through an alternate key which yields more than one result +3. Requesting a resource for an unsupported alternate key property ```http GET https://graph.microsoft.com/v1.0/users(name='Bob') From 9cac927882b3421a75f372e706d76319576d62ab Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Thu, 5 May 2022 09:32:38 -0500 Subject: [PATCH 363/729] address other comments --- graph/patterns/alternate-key.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index d29e821a..508baea5 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -12,15 +12,15 @@ The resources exposed in Graph are identified through a Primary Key - which guar Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also an unique property that could be used to identify it. -While it is still possible to use the OData filter, such as +While it is still possible to use the `$filter` query parameter, such as -`https://graph.microsoft.com/v1.0/users?$filter=mail eq 'bob@contoso.com'`, the returned result is wrapped in an array that needs to be unpacked. +`GET https://graph.microsoft.com/v1.0/users?$filter=mail eq 'bob@contoso.com'`, the returned result is wrapped in an array that needs to be unpacked. ## Solution --- -OData offers resource addressing via an alternate key using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. (Note: this is a hypothetical sample) +Resource addressing via an alternative key can be achieved using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. (Note: this is a hypothetical sample) https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0 https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com` @@ -29,7 +29,7 @@ https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the em --- -This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides an useful alternative to the client; while it does not work if the resultset has more than one element. In such case, the workload SHOULD return `400` +This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides an useful alternative to the client. ## Example @@ -40,7 +40,7 @@ The same user identified via the alternate key SSN, the canonical (primary) key Declare `mail` and `ssn` as alternate keys on an entity: ```xml - + @@ -76,7 +76,7 @@ Declare `mail` and `ssn` as alternate keys on an entity: 1. Get a specific resource through `$filter`: ```http -GET https://graph.microsoft.com/v1.0/users/?$filter=(ssn eq '123-45-6789') +GET https://graph.microsoft.com/v1.0/users/?$filter=ssn eq '123-45-6789' ``` ```json @@ -97,7 +97,7 @@ GET https://graph.microsoft.com/v1.0/users/?$filter=(ssn eq '123-45-6789') } ``` -2. Get a specific resource through its primary key, and then through the two alternate keys: +2. Get a specific resource either through its primary key, or through the two alternate keys: ```http GET https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 @@ -105,6 +105,8 @@ GET https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') GET https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') ``` +**NOTE:** When requesting a resource through its primary key you might want to prefer to use key-as-segment (as shown above). Also, the key-as-segment does not work for alternate keys. + All of the 3 will yield the sare response: ```json From 5553331a9963c0cc303435e2852a618da260e3c3 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Thu, 5 May 2022 11:43:57 -0500 Subject: [PATCH 364/729] Update graph/patterns/alternate-key.md Co-authored-by: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> --- graph/patterns/alternate-key.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 508baea5..83dafc94 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -102,7 +102,7 @@ GET https://graph.microsoft.com/v1.0/users/?$filter=ssn eq '123-45-6789' ```http GET https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 GET https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') -GET https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') +GET https://graph.microsoft.com/v1.0/users(mail='bob@contoso.com') ``` **NOTE:** When requesting a resource through its primary key you might want to prefer to use key-as-segment (as shown above). Also, the key-as-segment does not work for alternate keys. From be6a0c251706297820a91c90367fae541ce05df7 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Thu, 5 May 2022 11:44:45 -0500 Subject: [PATCH 365/729] add ssn in example response --- graph/patterns/alternate-key.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 83dafc94..80cbf03e 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -117,6 +117,7 @@ All of the 3 will yield the sare response: "mobilePhone": "+1 425 555 0109", "officeLocation": "18/2111", "preferredLanguage": "en-US", + "ssn": "123-45-6789", "surname": "Vance", "userPrincipalName": "bob@contoso.com", "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" From bcc3eafd7bd6583a9542c998eff4fe418c702890 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Thu, 5 May 2022 19:30:47 -0400 Subject: [PATCH 366/729] updated pagination ref and patterns table --- graph/GuidelinesGraph.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 2ca83c75..d8b46813 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -182,7 +182,7 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | | :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| -| :heavy_check_mark: **MUST** support [server-side pagination](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_ServerDrivenPaging) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL) for collections | +| :heavy_check_mark: **MUST** support [server-side pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL) for collections | | :ballot_box_with_check: **SHOULD** support pagination $top, $skip and $count for collections | | :ballot_box_with_check: **SHOULD** sorting with \$orderby both ascending and descending on properties of the entities | @@ -417,6 +417,7 @@ Recommended API Design patterns: | Evolvable Enums | The ability to extend enumerated types without breaking changes. | [Evolvable Enums](./patterns/evolvable-enums.md) | | Change Tracking | The ability to keep API consumers in sync with changes without polling. | [Change Tracking](./patterns/change-tracking.md) | | Namespace | The ability to organize resource definitions into a logical set. | [Namespace](./patterns/namespace.md) | +| Alternate Key | The ability to uniquely identify and query recources using alternate key. | [Alternate Key](./patterns/alternate-key.md) | | From 6a5ee37c49d8d76983bef9f39758a83ee73c34e6 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 6 May 2022 10:12:35 -0400 Subject: [PATCH 367/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index d8b46813..0bf5685b 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -182,8 +182,9 @@ Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.m |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | | :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| -| :heavy_check_mark: **MUST** support [server-side pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL) for collections | -| :ballot_box_with_check: **SHOULD** support pagination $top, $skip and $count for collections | +| :heavy_check_mark: **MUST** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL) | +| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using $top and $skip (or $skipToken)"| +| :ballot_box_with_check: **SHOULD** support $count for collections | | :ballot_box_with_check: **SHOULD** sorting with \$orderby both ascending and descending on properties of the entities | The query options part of an OData URL can be quite long, potentially exceeding @@ -417,7 +418,7 @@ Recommended API Design patterns: | Evolvable Enums | The ability to extend enumerated types without breaking changes. | [Evolvable Enums](./patterns/evolvable-enums.md) | | Change Tracking | The ability to keep API consumers in sync with changes without polling. | [Change Tracking](./patterns/change-tracking.md) | | Namespace | The ability to organize resource definitions into a logical set. | [Namespace](./patterns/namespace.md) | -| Alternate Key | The ability to uniquely identify and query recources using alternate key. | [Alternate Key](./patterns/alternate-key.md) | +| Alternate Key | The ability to uniquely identify and query recources using an alternate key. | [Alternate Key](./patterns/alternate-key.md) | | From 65dc630b446e44946da76d6dfcdf7f4f87490392 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 11 May 2022 09:46:47 -0400 Subject: [PATCH 368/729] graph LRO --- graph/patterns/change-notification.md | 0 graph/patterns/longRunningOperations.md | 189 ++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 graph/patterns/change-notification.md create mode 100644 graph/patterns/longRunningOperations.md diff --git a/graph/patterns/change-notification.md b/graph/patterns/change-notification.md new file mode 100644 index 00000000..e69de29b diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md new file mode 100644 index 00000000..b5932c58 --- /dev/null +++ b/graph/patterns/longRunningOperations.md @@ -0,0 +1,189 @@ +# Pattern Name + +Microsoft Graph API Design Pattern + +### *The Long Running Operations (LRO) Pattern provides the ability to model operations where processing a client request takes a long time, but the client isn't blocked and can do some other work until operation completion.* + +## Problem + +The API design requires modeling operations on resources which takes long time +to complete so that API clients don't need to wait and can continue doing other +work while waiting for the final operation result. The client should be able to +monitor the progress of the operation and have an ability to cancel it if +needed.The API needs to provide a mechanism to track the work +being done in the background. The mechanism needs to be expressed in the same +web style as other interactive APIs and support checking on the status and/or +being notified asynchronously of the results. + +## Solution + +The solution is to model the API as a synchronous service which returns a +resource representing the eventual completion or failure of a long-running +operation. + +There are two flavors of this solution: + +1. The returned resource is the targeted resource and includes the status of + the operation. This pattern is often called RELO (Resource based + Long-running Operation). +//image +2. The returned resource is a new API resource called 'Stepwise Operation' and + is created to track the status. This LRO solution is similar to the concept + of Promises or Futures in other programming languages. +//image + +RELO pattern is the preferred pattern for long running operations and should be +used wherever possible. The pattern avoids complexity and consistent resource +presentation makes things simpler for our users and tooling chain. + +In general Microsoft Graph APIs guidelines for LRO follow [Microsoft REST API +Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/graph/GuidelinesGraph.md). + +A single deviation from the base guidelines is that Microsoft Graph API +standards require using the following response headers: + +- Content-Location header indicates location of a RELO resource. + + - API response says the targeted resource is being created by returning 201 status code and the resource URI provided in the Content-Location header , but indicates the request is + not completed by including "Provisioning" status. + +- Location header indicates location of a new stepwise operation LRO resource. + + - API response says the operation resource is being created at the URL + provided in the Location header and indicates the request is + not completed by including a 202 status code. + +## When to Use this Pattern + + + Any API call that is expected to take longer than 1 seconds in the 99th percentile, should use Long-running Operations pattern. + +How to select which flavor of LRO pattern to use? API designer can follow these +heuristics: + +1. If a service can create a resource with a minimal latency and continue + updating its status according to the well-defined and stable state + transition model until completion then RELO model is the best choice. + +2. Otherwise a service should follow the Stepwise Operation pattern.# + + +## Issues and Considerations + +1. One or more clients MUST be able to monitor and operate on the same resource + at the same time. + +2. The state of the system SHOULD be always discoverable and testable. Clients + SHOULD be able to determine the system state even if the operation tracking + resource is no longer active. Clients MAY issue a GET on some resource to + determine the state of a long running operation + +3. Long running operations SHOULD work for clients looking to "Fire and Forget" + and for clients looking to actively monitor and act upon results. + +4. Long running operations pattern may be supplemented by [Change Notification + pattern](change-notification.md) + +5. Cancellation does not explicitly mean rollback. On a per API defined case it + may mean rollback, or compensation, or completion, or partial completion, + etc. Following a canceled operation the API should return a consistent state which allows + continued service. + +6. A recommended minimum retention time for a stepwise operation is 24 hours. + Operations SHOULD transition to "tombstone" for an additional period of time + prior to being purged from the system. + +7. Services that provides a new operation resource MUST support GET semantics on the operation. + + + +## Examples + + +### Creating a new resource using RELO + +A client wants to provision a new database + +``` +POST https://api.contoso.com/v1.0/databases/ + +{ +"id": "db1", +} +``` + +The API responds synchronously that the database has been created and indicates +that provisioning operation is not fully completed by including the +Operation-Location header and status property in the response payload. + +``` +HTTP/1.1 201 Created +Content-Location: https://api.contoso.com/v1.0/databases/db1 + +{ +"id": "db1", +"Status": "Provisioning", +[ … other fields for "database" …] +} +``` + +### Creating a new resource using Stepwise Operation: + +``` +POST https://api.contoso.com/v1.0/databases/ + +{ +"id": "db1", +} +``` + +The API responds synchronously that the request has been accepted and includes +the Location header with an operation resource for further polling . + +``` +HTTP/1.1 202 Accepted + +Location: https://api.contoso.com/v1.0/operations/123 + +``` + +### Polling on a Stepwise Operation: + +``` + +GET https://api.contoso.com/v1.0/operations/123 +``` + +Server responds that results are still not ready and optionally provides a +recommendation to wait 30 seconds. + +``` +HTTP/1.1 200 OK +Retry-After: 30 + +{ +"createdDateTime": "2015-06-19T12-01-03.4Z", +"status": "running" +} +``` +Client waits the recommended 30 seconds and then invokes another request to get +the results of the operation. + +``` +GET https://api.contoso.com/v1.0/operations/123 +``` + + +Server responds with a "status:succeeded" operation that includes the resource +location. + +``` +HTTP/1.1 200 OK + +{ +"createdDateTime": "2015-06-19T12-01-03.45Z", +"lastActionDateTime": "2015-06-19T12-06-03.0024Z", +"status": "succeeded", +"resourceLocation": "https://api.contoso.com/v1.0/databases/db1" +} +``` From c995fab7d5106f14de06b3973cb092313fe88f5d Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 11 May 2022 10:13:27 -0400 Subject: [PATCH 369/729] updated to Graph URLs --- graph/patterns/longRunningOperations.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index b5932c58..8c9f52f1 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -105,7 +105,7 @@ heuristics: A client wants to provision a new database ``` -POST https://api.contoso.com/v1.0/databases/ +POST https://graph.microsoft.com/v1.0/databases/ { "id": "db1", @@ -118,7 +118,7 @@ Operation-Location header and status property in the response payload. ``` HTTP/1.1 201 Created -Content-Location: https://api.contoso.com/v1.0/databases/db1 +Content-Location: https:////graph.microsoft.com/v1.0/databases/db1 { "id": "db1", @@ -130,7 +130,7 @@ Content-Location: https://api.contoso.com/v1.0/databases/db1 ### Creating a new resource using Stepwise Operation: ``` -POST https://api.contoso.com/v1.0/databases/ +POST https://graph.microsoft.com/v1.0/databases/ { "id": "db1", @@ -143,7 +143,7 @@ the Location header with an operation resource for further polling . ``` HTTP/1.1 202 Accepted -Location: https://api.contoso.com/v1.0/operations/123 +Location: https://graph.microsoft.com/v1.0/operations/123 ``` @@ -151,7 +151,7 @@ Location: https://api.contoso.com/v1.0/operations/123 ``` -GET https://api.contoso.com/v1.0/operations/123 +GET https://graph.microsoft.com/v1.0/operations/123 ``` Server responds that results are still not ready and optionally provides a @@ -170,7 +170,7 @@ Client waits the recommended 30 seconds and then invokes another request to get the results of the operation. ``` -GET https://api.contoso.com/v1.0/operations/123 +GET https://graph.microsoft.com/v1.0/operations/123 ``` @@ -184,6 +184,6 @@ HTTP/1.1 200 OK "createdDateTime": "2015-06-19T12-01-03.45Z", "lastActionDateTime": "2015-06-19T12-06-03.0024Z", "status": "succeeded", -"resourceLocation": "https://api.contoso.com/v1.0/databases/db1" +"resourceLocation": "https://graph.microsoft.com/v1.0/databases/db1" } ``` From 5132f81a830b362f0e7b2eef6f1519de5b269bce Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 11 May 2022 10:16:50 -0400 Subject: [PATCH 370/729] ref update --- graph/patterns/longRunningOperations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index 8c9f52f1..26e10503 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -37,7 +37,7 @@ used wherever possible. The pattern avoids complexity and consistent resource presentation makes things simpler for our users and tooling chain. In general Microsoft Graph APIs guidelines for LRO follow [Microsoft REST API -Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/graph/GuidelinesGraph.md). +Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#13-long-running-operations). A single deviation from the base guidelines is that Microsoft Graph API standards require using the following response headers: @@ -118,7 +118,7 @@ Operation-Location header and status property in the response payload. ``` HTTP/1.1 201 Created -Content-Location: https:////graph.microsoft.com/v1.0/databases/db1 +Content-Location: https://graph.microsoft.com/v1.0/databases/db1 { "id": "db1", From f3371075fd1884758238fb1b40b0766f0a9441db Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 11 May 2022 10:38:13 -0400 Subject: [PATCH 371/729] Added sequence diagrams --- graph/patterns/LRO.gif | Bin 0 -> 14513 bytes graph/patterns/RELO.gif | Bin 0 -> 11528 bytes graph/patterns/longRunningOperations.md | 11 ++++++++++- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 graph/patterns/LRO.gif create mode 100644 graph/patterns/RELO.gif diff --git a/graph/patterns/LRO.gif b/graph/patterns/LRO.gif new file mode 100644 index 0000000000000000000000000000000000000000..c95bab5f03dfb17725c7c10d56774e928d98c8bf GIT binary patch literal 14513 zcmX9^WmH^UuO57GmzU!1E(68gp~WdSSaEmPGB^Xp-K9W*;#S-p3dP->;x?CW-Tc@m z$$s{e>>oMnoUF(z%7KKyukeX*_kjNwJ^%m&1Ofp7FaThQfr0UE<-2KF^PXA6q=$zG2nywknthK4ikkKD@c}`b0s;b>EG@xNQ6T8@`vQ)t z2?+_YghHdDz!4Dt%$!NI|%Whkr(3SEYcj*d=FR<2J*)Xd(!lOB(T)X(0X zk48*RPBtwspO02fPM*)s&Mq&%rvSU2jk=qST3A?EUT!*{oV=T@xtp!8JU?GvUWP40 zYp$=~pX=-EqsPZJcXv&&CfIUQ{oUR6_V)2{$>jO@yA4}j-rwJ!yuNON!Dg?okB^UM z@9vgiP3Pz5*VmJGckdkUj{0}p_4W1L-Rv?9c6WCNgEc)pJ;C0O!(eRxtAPL20ssgD zz+O-8C$n2UfYpR{gS7>HkvOzU*$Q=q(sn@JF8txTqM-yT>1Z;=`r?sfCe2F2k@}Lc zG%oYeY{g$zLxI;ARt*dG1p|2!VeoOOxLa~r3dyeHJ~-6Pxl%<+DWug$ZDFs%uE|ox ztk4g{bP)271yxJGtmi6?#+uHS>dYurg159GNoGp_j@QPTe{TE^MnHdSo{Yx%DJsxS zc+yn2Gh|OJp4eSjlc*cU>-Rl!-&vQMiir1{N}IbyOR}aO(I0M-F!T=$_J5rV?oL-) z+)maf+M6#nn(g{ZMSHr*CWez)O(r{9|D`U_sQ{?6njAL&{<0h5@b`2;(61MRU9>y) zAsny&bTXMT_VYRcLN<3EMgi#bQssIhJXRQ_{D4Zhp&*}#L-}SX0&+Prsdem@ zERhx{BoEF!wruMgndjkFBn^L&1nPrgok3M8Z5LJIDXqmV<|0PvLbhmEeIw}X83VhKQ6{dgX=uL#Zpx_xqQ<2*Q)*GX9e(tAv-a8 zkxeOOJ^x!nq`i$2%OBg5^Y%qoyJ*IkUK^Im7AlE>lmzekbJmudS!FInV*AS;AY;{K zF9t0e7st=q*|S<(vXAEjBwwqzRc5&I5%b?l;cL4w)9kNDFvZDSn$r-ast~mx?Jf!_ zcU+Mak>MiO-AwnVzeYMYLu2|28^#Zj%Ur)Z5$QdRp>gyO>}c}NXgS>e(fe9`yI|;L z)F?fGh;Y>((A@*5=Pj$gTPE>hlcfwXG#i~mP(Qm}^FFBN|J!o|yGC2`P%w=cw@0^_ zM3OqhydN#d)CFlx>Sek@nzQaHf4aEKYNLIw~)#X**ib!&g;L z@pDxm$LRj_aMp6L4j$4%yoNBTf5h@V{6XyWa#hj%=F442IXi?HcJem0CG)_c#G=8; zCvv^&Wzun73uE`AO#3P(_xrCOmayPvu=(`!rgMF0Nw>;+t zS)m*LDZ=Z}FXBmRvFlmrG6bi}a@qW zc?9#a4E#7VCY-f5A*&b(=G~DD?Zt2bme+6}#nWY^#0yvshEe!?5ow38v_*yz(Yl*K z_$rFyD4ozkLE5fmisb}YNYp$mOHC6FArLWR;c$VVhRgd#YQjG`!%4BOPcs?C7Lj#6 z$i805*qe(WqVE2a;(JgInQ%5I@?uJmBE3$8s1(x>k0{8UU$SgUP;-3Vijv^MjZPmg zX81T;$JQ|#7P2d+30xZfoO%5{+P_#R`meZB>Q!vpmm#F@5(66XpCil1OE}0Yq!nCg zNym$JY40FE&3)kgs4O<9+-zd`B+b*NRrWsC)c-csxrS?6DHdq9>9!W3LJz`_!(3|~ zx6Yi)77C|R_XYRJ8v;^WlS}Y-krgEOaXw&34hh}++>g4~xaOSEmCnX{hzcxRQeAM- zi4l!Xf7?bNLIFr1(PY9ob;AST(g$QwH!&l9Z;GfK%Bko68?ma@Bnatd5Y!!=UGc%s z9u~1C+S@__{JkmtFI`r_YjiF*)V+9)?MQJARed#O2zBfG(Z^gD_}Jwe;OS+BmUP=p z{53)O3DXg7j1IgQ8X_^Gsw^k`Q5V^0QQ17in(p!w_6I;WLV0&NN9pl&f) zcEN1w)9p`(XDi7VT;cFp0sxtjwIKqRZkzBzo%h5sxyh#nFWW>1`P(!}+17Zs_m3L0 z?jezeOC2wfrZUd}dy9#yv1;SOAFu(fzS}|DfsEXgg6H9C$9pJt^R+~CU3WPdS6j>) z>3z!}CI?)9$m%K&U;UGv9M|2lZnwdGo6mJAG4hGruZ8bLZS-eOS<36znQ0wUDMNhv zA21gUbbK@7DwsB0kO1yfl@IwShybdne@}v~5XY+sT>}^qSlDoy}hvia`4z?P4{sK zHa`x-0Rr2%u!35K1dQNC?%*uY{b`!1v3e4JKml-xdH?~@J~18KFud=Zn9|6u0E_}- z+orVoG;b$1!QlDywy9qLJ(!)h7nvD)A_;($i32Kx0l>ZpYtTTJz}0ZpgsTPr7Niem zIIaQ3toE_MCgwE*m-|}BhdMOM5Gj9f< zK*C;Vyj|m#Xt%p`lQqUf<-~whzWrCpjXoUMOvaBCKn=JJERi_$cZUOrftrw03(&AM z;b)ZAQA#(+>`lC+hQt(jcB`oWhGf=+{;=4_e?|HE%UpJi z8$Ab>gBMD6vk*?VGfdogU5A%+p}whcRF{Lq@MHS+!=^`6TAYngGHIRbd2^?mpFKUJ z&xk6+)lf!wXL8>cC^EU)gxpPt$vN0ZIBvQ*lX{#o7&!p#S_$#;ZK0nOu8majG)sPee>n5E zm>U_Tetf%9aCuy#9zXfuWH6G2B?4Ht525$(J#FQGo_app?YTg>DOh~z&7yEDCssQS zFKPR;SiRzzMKO~pUxlmkGmhl@14cfq{jWObgLN|@00MblHMjFJ`jXipx8dW+ExL-1 z4!cwk2-u2DfO!uTbzCK4|9<#F<8z>!+d5N*cNkO^ zyuhW#o~|ZqBGh&d`z235!N`E;X}3!WcNH#+A!L6uuYfBQXM?Z)jw$}9^j-;aUU-5& zK4o6J^u~>mdSRYmA^}vg1rx|1f@gk!u$4Ewrx}C1>THAzvtS@7J&bY7RrBkY4SLgU zS$7r^GZ8L}RzgF;lF;Hkx6-d*IwA+DayNlLCfq0h5Du2{zWcV7)~=P+zJ$r$9#$(q zIEl;GYr%s#JwODoN&T-NjJ=C^0(P#f=W)YG9#3(8IDf;aeX#QZVf+1!j7c*MJ`9PN*VrXQ$JK@t z7(jGWFzg`&v?pTw;d}xl!VQ$TQ%HC_(xWPU)g19QJR;Om=Qc_Q24=S;?hJ+1-AAC_ z8m$MLJ8C1}K@y(QvBAL!08{u%5@b+{TXb*&J|TeeQb!JC$sqyH5Nt_oqZz$mnR*B_ z?mZ0JwL;56f(~_mg}c;}CQ76p`En0T9*{M7Rj_!lP68yxN+`njmp~;)5=!ag{nH~% zkzLSVfcJ*K+LrY3nry`Y!~KBA3S z=v@b^PvE3>?8AXk(?XfkczsgH%8~a{)An{#LzmKNWYeWAC7Jo{Z1gi&?>}eKt7Tj2 zpzWpSW(3vf;6NY6zmRwkbE%gC6h`|qV)ZreZ!^j~6zGsNU|}hb+wv7hLU;X{FY{Tm zyXwE^l@?_(QuVVrzh$&4>Hr6_x|XsnP_w)H)owm#{`AV^DbCsrSBC6nKJDIQ&lD^D zwn)99(J(=hzLEJNz^CBWkb62J?cpw;Tr87jk$tmd_E}4L$6fiXHP7uPca~qV-dygr zOrXA4&NEGAw>bBuHUG;^ZZ~4SuZ$PaS**KHuE1SZPm6%Gt$=K~fa0k@_Dal?AP3ka z)32Y$wEX#VgIt@9v=d?>7ikd>X`u{(B3-5o34v%^vYb$|81a)BFKMyVaRH5i41c-| zZl+L{8!F8|$&h}DNBok{!X;V;B|5$(+AP8~|MF@GBp5RbO)FJ9<|NhH#7@IXtTRh( zZA%PnWs+~ir;3VNrA51V1ud0C>@v%Iqe~s0^3G($U>sVx`lh0)$N1WF!jR1J=(h6M zz+W!bP>g_P#!`>p>SpKad{G{fOJ@qZ=$4iI7oG_ zIN!ItMEFO;Q+b(EMWullrv*~9v0}T4y!s`!&cS{2XxQya5BG5|NBG=sogLoaZVc6X_GC zn**PlgFm!{eryT<(vncy?Dqi}kdNYV*{Dy2#C6k<#41YTir{Y7?EL}gJJ$Sdr8W1t zH8`dvGQTDEq(!S601B1EON3ARz@E|Gn*X7_Ub&_4M{B@z%Rmy64i}sTUh{`Ucs2ln zpfiH#G=h|KhcUEcw7p||rDO8BW74lpINpnquvglIUEB>7+LWKnvS`eCX)=(sBBu>#6I0Po{D*D;S~mder5lH=2T!C2myH-tqK72A2e%48CfL9AgmDr12+sjSX?|KE0BFuG$*ReQC&A9m#va{mF%#?((rfWVCX%s-k0*{L^TeA*lLv zv_U2Bm&#ae?8j!qv5wf(_Kwlk*wO6OvHsJszVWdhwy`0@@$ss#-&La{;{sEwGZe2gROGWXqO){HvkU>VOgXdT@G~rHv*Xm*0~m91{K!?CvjRC}s}ggaWEkc+ z7>#Y?Q-b94L2%RRtkeHV&r9XZvxm%M+0B#m%`1`rVTAiLc|1=n{pWMeAL@`lnYMp+ zdjA-ZFTlesfRF!7NG+J<{ISj{w3=YE%UMu*UASFZ7^7KqCZG3o$ab%0^9fkwSX)eO zT^xb`8<;a2o|76X${cC*m!b17M#f*??Z2_2vq=+Ci8+iZqDxdcOPStF-t|+3RZE2v zisq(how4ZV!RRe>*e%_IMX$^7J6CYlz~e z8=Gqu{nP6nP3xF}o3QEiC!?+7iS={Ljn@L?YtapTqYXUcb$|xoj0&eqVzat-3z!Qa z;J|s~LS#wALKoWxn4*q5XgW~gAaks~;J{(LVZV^?5>0Ns;%+o!ZcDLmV>)UQyzL-S zVUR5#PExIkoNw#YY&mrAK47A8aA4}e#I}0ZcF1~mN#54+19olCw-jr3X}JY2@eW+Y z4&03oJOdBB*I5_3Hv;64*xs-;*0FYLk=zI2?#cJzuXc7O4#)>l&pLPZI@c_6w>c*d z1?drda*r~*j6=6o-~#n+$628L^TuZjfoFen&wkY) z3r~!RoXz#FqU4I5?;4-)i=F#no#{GmGpTLrzn$JWoHA1&an+pvp4<*m+ZU(UmFCzw zi#s}?xP%wKgqu3)1k88akBV24FO!{}z%bL(z;yiH@w_Ye!7nrybq-+y!TX}2>k8p52Fr&-47T( zGzmOVgg$(m8jDr8i{7A4*m$TBdE_8^Br|*bEV3huwI_LMJBiYl!?IWqwQ=xco zIZU@yoI=Q9xq@>38RNMzcDXX{V^1(k-No#y21eV(=(7e!N!N3~$;)8S%W&Sy=D zjh9K-%QWTdEKK}$-sH7$75$gPE6Eym=jHQ{4(v{@mk<`*-Aq?+tlb^l^I8Wj>SN!ML89 zGaNpy)iaSMLs(?bchQ}#yz_5HB8JoXWmz0pR7!JUyQF!7hi_NjPpN&2LnhOHZ$bBu zu-_FTGn?oR91#Ye;Oc@*S%(`#akOwGQ2VZ(de830eoCPW5-3XSX47Ie#Et7sQ3ay0 zPE+D7?bHr5BD>a!VO-5|$$enFu5IW@<-MdbiG??{=F2YEU%9(rj<7#K9S(*y}N9x~I&`x^ocEyC?(sz_ZlBp3WVb;hYn=+0Uyk z+Gp@ped_6kS3?qozClCU3H~5a=uW8Nz4aQwHyepZZXPR3KftG{Dkz#f%RTQM@kveI zD2kRyQI=mjTLeja9wt*YoJWlu#$V`YY^uJ{ZCQv>axy~IBn?EL~X#@wU}RK|-X!?BOMqQkhj z5QlQ3+QSH9I$wMmlvwuc74$ks0^UOtyNqhcdN2xB>_TroFA3O{bAQTEmoXehi0=$% zhh$j#R$+-usJ2zsBCvZ+0_gCCAFv2Zc2`0YQL3f0EF9KY(&Yu1M&}XsYU7U#ReCJo z7M|}hLfFsQA1zFa9x|8@9h-LZOPc|~VHP?#5)*Aw$ift57Y>hm1$`V$4mohO0K+s) z8o%9!BiFW*pG&z<%TyvP7No~SD%Cwz*_|t2^RsL-8mkKg{sN6 z;cQ#o2Xeo<%P9KHe*@gb`J?;Q=vIFH6EIKNpv>r2`SR0nw!CfGTJlHy?QdSonqUPP zlk?kyNLq4>FQ_cxNHy6^n2b%YdovNM+{S~Si0dx?9b z7EEN~QauIk@Wr+UIz(b=3nOkKGc>xI_z!4Dk&Kxn5Fw^d?hHPWOr|LL##*)*rI%(T z1BbH0a89vbtLE|Xd(rrEU1ES&G9LyY-{Ndk`GmBvJYISlog)(TMGFv2&p#8JV=C+7 z&7;)-bqNU*x3v_WQ>eFw$i&d{1EOW(=ngcAJBMXCMz)HnR=O2n^4nJ&kolQ-CHzr% z8~UV{UNBHHJgqpY-l+DI!6zL##X`L#-;jA+YLo|7?WQvBLAq~DhvkV9!`!jXdO))5 zqX7vf3rg#Bs}hldL@$e9pM6`l^?MPiLKNIEoE4A+yX(|EoAAl@O^1b7g{C~y z6hu8LGPodd>iEtw=AV``q4Uf{BR61{_nWhzWX$SVxdVTD!^a>Gk*)AZQ^0>&!UyC0N@rn#8p+9Fy*3Uje+^JD7~&;rY9p*6Q=aPl zKG>X!UoFqcJR+HVN3XXeyAoAX0sEviNe;HqEIyJ{U^91f zlG6;&bLYfUF~?fmQ1Cb_Utz3$yR8B765C_Kx$kbz$E1J^b?3iv(L^#u@=)Z7V*V7p zOycf;{0ckYPBwZVX&tlN7d{1CdDxuQgmTwIl-=~fRRf2u(*Yx(1DJHiaqho1i3Q!O z7SlrdW@N5$?WB7Geab+-|7dk9UAsz8li2W&ha`+4?gPnLG<^lJUsbQA$hhT*`%v3hMdcvfT5>VUjBm`SLCcoEN)Zdw`U~8Mzm4ZS>lG14eRD6S47HZ# z^!@lf)x8uo&PM8s@JFs$_i|Zs8I6Qx>fA1@GSyC7?F!QIa*FsL`l2VQBcyG$MDf)& zopuUG!r3iY@jrb;Po+@5xOd-8q>swjTSd;1nR~ncg5kz->zPaGrFb@tXvRYD@mp>FC+KvUc&!t7A{p$#-#O{eIA^t2I19UPgKYR*L<*2YTj3 zCYARe3x79Z?r}K&hoyGzyI%VEWQh6cR$IkGKmTM>Z1Tx=^U=egY)!^D?B|^()W>1% z8tC`37w%hT3&e7v!M0{et-SI&-^V4)>uDc4kD6fic z+hhR+bD8H#+3u$yU*%G)ib`Fp6Mf>xMe;u$BM|aae8J~}VTz1~JfkfK&iQ5PFl>!FY)?iq`dz&;cxFj!S$} z`|*tca_w;e!F_=DTX*NbWr5fH-8ihY`Mj>BYV7&r4S2sr?5sP#q*sd?W51FOWA#?2 zm zR4$S+Jd$xZJxbI)By_|ScywtqOeDLt=XE`a>5`n4J(OfA--Y7iNYE%$0F-o6|A`b) z@8Y-telQpJvNZIv_V=>Q_p(nX{9e1cTrzSy3)oGnqjPtwB@!Qi0 zrYyaM)LYh|6uK1mF@ha$cQ#e`KwnSSCbff#lu3V@i;(;ia7gJt`7kE==w~_Gc_6YT z93eq4!=@~4UH{YAkQn-quL#7iBg)%Q#Ne7~f-GuzboD z#PNA-_*mn<3&v-ACC2zep>&Ggyo#ujifPs(DHh4;F9~V$!cpHxT#6LmY8B)AMzS%Q zbH)>L4N(gaM_IU){zFv^*BmL*YA!CuF4RJ`wNomS5iBReuHcJe3T6CZA?S}2QDfi4 zCJg$?H}*?rtU+t6(PFI0eXKcrtmXSyYw=jynTX)WI%OMHNvjp*7mcQl({@3qfRGR` z6j8;jPw8rUG)ZB+FSapnykUSYYS4Y$G)zS?StaSm=qLgIXh&j6F?4KK2+_6fBgs*Pxqty+;-FUa8< zk-J)I`||`F1X$}Yz{3VXY@heZh3G4#1n3nMNdlfl0WhZ*La-a$ibHyBST0|nwvnio z$~uWQ4%JdBX}gSMp&|jo6NN7k`CUO?PX~?t)T!*Iq|v8QW2Z{daAzQF0dRSkt{5mt zc`Eu-YcP^&)@PbVk?D|aXjknNXfATt6%!|-N4`LSDoMbQ1i>&p(U^peEnv11Y5ccuqahXpsQ}Xfct4DWOZbVlIw8Ot=@aA! zgUqb&r`ZVW2)uNvm>EKpGh(IzEN1O6Ziz4|`d(J{Quj}|LUS`H%-Ie$5oCmOTU2vO zLWp(q^87hdAEh9a+|j7`q;Mo5*NAAf%=42&f2;$L488wQHZJIP|EZeQ zwqqX;ON?{~h(J_5(^5br>YAi;N9)(bz>gK9$(CrZ0y^qsDonn)2e5teOS^&?D| zY@(OkZu>F=bP^`?#mSb!nDm{tm%bb6JFyr9MCfN28AP-!+4~}*;sQA<5j35(na+Im z{BN5A)^vhYA*O2!!LN}ajSHddkg&#uaCS&U<3bEOq(O86q6&0qT<{kKHX$uWkSsdz z>xN4#TF*CltSxwo0v){oAP4j$Czw(0QAAYE6eJT`v8>a-NY|5@_;=3Mc4a$&NEM+VwF524-30LAEQSuq zd`>>Ym1BHl1UYDAdK*A}yD>RKYQ21I+=6A-eB(9Q^$@uoP?yI6(2>A(Ofz-4m|l+8 z<|W2_DF{2SK)bDtL5bgdY|LYF4hND(*ZO?uu>P+~0UVLJSxCL5RK;Y%491j;{@uHk zK2kkB4esZvpxCD>{0okN`pn*}uocPbC921^0V!M`) zYMbtnoh21LDBq;I1;Ioi|CXy#`5LGqkz3TenJl%V52wvrn#v=Vgt%!IL4(ENh*TN{ z5VImhcs36>+`%Rc@l3?#DK&?a6N2w5q%Uo~=3Q3?81=QQVUb|ux?;-zg*%wvCG6dr ztS1RAj3$r6e9I1g{}Tv(vS6p!>wU~0#@q5mhBw}tB5>K!8ATV?hB(*k z!Bc-lK(|C;q3~!LHxXzwtwHtH5x&JF8U#fkWrmmp6a5CjvvI_(jn?frYZSHS|M%DW zG2hDT@sRW3;J<4oLLsadx+8I>V0Ukuuse$I)+1WO!!K5cUuu4~NA&rb~Y|eS~7iG^@XYVpqPq z&~&ykc(%H5wzeO4wtjcEfqcG6biO6f9N3L?Lw_#MeZE-2&m4Dlkw3b-{~d2afIxfZ zGlu;U#rNJC%;|lhsRc)@EytO3w!F*{vCF75?XY_gm_ zk2yY1;@|GazUTm9`xh7MPO1o(;W(FYWf$-XDtG7h$hywFikG7x=fWQsz@V?d1nUMg zaR}s5-QRj^Eqc~FpBp29h&8`bOu8Q6> zuEaym++HzUuxk$NF5ZJS+^;Qo^8YZV1bDsv2|8T}aq=qCWNZX{N}M>bb_d@%tMH0+c1sTGpUG-OK} z-+M$U44DU3coq}!$BsG6+*vpQY3Gy1;!j`}V?KQK#?7~s+}8GsvJNK7j?ElS#MoNc z10G-RJ-zO|BJVZa`{y?cN>CM)=F#~3`@IkD1MlyHP`ou{7)39y&Zd`wLBpoYMm8s3jcE zsL1~w9)xf{dfD@T|BCM@kC3{Eopv85l_wZ@BOjbCqeM2$3h$c+9LgCIP!U&&9QQct zd*siMEJaa_7dK5HEAw2N^VNc){LlrKZ9J(7HU^rWxyMe}~%bRPKSC_L5XKliBH_F(7q$@>@o^OKypp0In_d=P0Z z;h(X)9a(&tICz=7f0;sgohE*rVSJqxguVLqq3~*FL`Jw?Hg1SQb$5 zD_~VbO~-j7sCHiQ?6q+`AgN*Urd#dIFk15p>7w7ZjbutjRO`llY->?LY=HLZRKqKy z;P!UvjesusUg>y4a|Q<&^VY6%_!DvtNsNug-Ni`*L6B|38{%u!lRvv{4xU7sHPW1NopIoN?_>uy%ZT^P^faD2l&UoP0LhrbK_AGcARe+L!WuN3NX+kc91{!yY2*xF=5=#*4TnX*#X?D9%(`fbz@9azF zbfPG0b3&d&zd>O*d3?CO3QeO_J$iY0y5~Igj1&)sBh`kP_`e#$#|NXbv`~d%3Or2G zUw}Z=5rhWb)ZhNAM^fV^^?jg*P)B=HgCb+OzlqRRw$Q||w>{`bF;>ou#qckC)5Z&( z6YP-KcGM@t)8zJNE89K(LjYL8)e%a@+I&q+#_H*(lhjN?{$qmzwzNz%f*&qQknlyN z5ht*|PnS5ZEi{(0tyhF*ozcV1ewP*aZ%Oi#fYyPe|1rUVL@?fbT0Vk9YjKV_ixNe; zIEPYc#@Xv;5-mIxcdvpxJXLp)0jh*7JgrZJwjk#;b*NGo9YBJhL_hlIy(RFdF8B%5 z8&#T(C4|TD1Fd_crbs8{B&cy+$qwF=G=?=Pn*s%vq+40fvQSy=>wBb{Vy0XT=y)k% zui~VicF-OZ^F8-$xuGs%TA;hZ0$q)M{Mm6rXMxrEILJyZ^>m!IE&2M8#VH0nZgsWUknMoS9nayF|IQx_m3P!De1T#fI5&}WW9{O@A ziDHKt-;1>Mk3SUH^P^D+wfbLU`xBBI%_?KPD~_PcN`My^N@sxJhPksF4r{^T#||HuP;C&jQqQLLQ3~I|GRIJ3-|09i-HgxA zxNaY>MC|b73`FDmp*coYSYPQ*Y{5Jm0lO_Ggcr-J5Anai7m_H-2GGCw4-MF);|W_M zk{5d7gR97Y97OLP&qR?)J;+=YmeKuO#h%ZaDZzIME#k1pTxX+1p_@5MAIXJ7@!y6R zp)s&lPA3#aBo4Wo+8>7ix_CV-z;a?HF2Gc{O^%T}g)r%eW$pe1sg)jge$!Z<=pd|I-_ASnSiy4%Hj}_9MJrA{@;X z4lzO^5ugw7x z78pbeM`<yMaL z_XNiR(+GACcm0l4VObUHGpglUl_Hf~!YXt$@*mDl%AEW7bodgM-5`&}a>s5ol}+xI z%C$@iA1&y^_rbJkQ_a*CrH^pah$&iVT7pm={QU+c7g-IeHS03Kx{$K;fLrc1snsv4 zD`M>c#5^}&EO`v2#CX*SIwVWMIO(H?NJU{?rwU`n+fNF~q=6;}{j7FJMf-k+z zHsy`@S&gUgR|y--ye#r4&3(&sDV8sc*~d&cF=H~sS*)?5l!b)`M)pTa0y9Aal}C}E zq_IM$lSRktpVoy8{}1C=7Cnbwf6m?ctE@y>4Gws8&cE|lI|i^ChSV>dHSpJXPT=^R zS85jqN#>))WVhE$7@Cf+h@AR;H*$w z>qN(4I8*az~{?gc<0<_e~YF13mWQYMk!8!c_R$>`uI=|_QsP2 z%BQgQ{t%9M3-s*gOQ%m`EPpCshmS0%0zWi!>kkI4Em^b_#|$>{{$QW{#}X*kq?-l5 zu*>sMt5O=P5texdFJ6dZz-ZG!Z*@;7zCWt5Oq7?82dpr*deT!n84rfCg-wc0Y+cK+B_X+VI&H^wigJ0OWn%qfTGsJ=$k{1dzpK`SD?`1+ zARFn!I*{00!8Gs*9L*+X`<48egq2w?GMBvc`}#HQsEFrI0aCTKwvZ~bWRFc+_bDZh zT?U#EvT&U2rg^o*pGkKH7=LBIB;`-JTd=i2n2*}H)dHcD?-5<7r@Q7HeEffvdc7y7 z)5{B6ok~9(CRhUa7Om?~8%mWGF-&xQ{}2q@*Hoi7Kz-UXXB-C_vRD~~dyvO@M2b}m zM1GWo)XbL!F4?N&oLZ}oua|E(l{v^Cd6*1**O(g~(tA3O;8dchXvs4c-C3SZq4I>V z=SXp@vQ86T#|^g{qB`p)4&BV`ka`WnE5;k<$+8Hv{(2y{^PaN!8N4bQphEgJ$#t7> zRS4SaGvFM_FgbC)ZBf@h+FnJt+R6R-W5N!WZW_J7s1<9UTfu0``_!rbVqP?}Z@M4K z-Rvs9z?UH^n_p6g5ZEtr#vcvT^0QREL>xJ67UgS$?ltWosO(zIV9iNQ+1d=om%2h<*xYi+eoVjrsaJAmM}^w(qC*I)xJ8C4DcZ zXyz(Z9Qoyl{QeEzC*rUt9Xx}zfSZs^rc5(3eTS)R!AErs#zEYtB;U&;t)Q^Olv~6! zu;I@`F$>R7#D@u00jeOMRld)10`PhFjEcAL1V7=I`(%|%YI2-?=2TT0vHtQBv4OUg z&uJ451R*eBXmV4}o?Yqf4rn5F=5Uk@$^!~;#HiO@C?5B68n^H=^G$KNc+7y2XTGz?bqTlGcmAw3-BE^5o7z!r>Qw{liA^V&_h2{Hh~31 zx5(?M&bU`m7z$-i=M<3$WJd7qxc(F0OFq$$Xjqk@?V3vjbWjRHjk^nXHolwuN{(0Q!HqYm8mw5mBSE zM*8K4BXUe~5>Wr`Anm_gfkv_caP|Yph&g&tEqUu1Go@GA8i*M@)qJT1nYbxj+?gII z(16E^MnsmNQ8liid|s&39Qu-jLS0A`c229AudDV@Bn{YA!&yEh&r&&@6_-&_FVI1O zN0LC>_2^Xmy7$GsLS9m5P}aDA`HEJa1FaxKGA)G8c0g0&YEx=5QsG!e$!bu^tluuj zLVmDVWs**Hp;&dDPHn$f?VL{iu2}tzP6N3_1B?C>QOPF?`p*m{pE>9?1xhr<=vR;s H0U!Sl0tdPW literal 0 HcmV?d00001 diff --git a/graph/patterns/RELO.gif b/graph/patterns/RELO.gif new file mode 100644 index 0000000000000000000000000000000000000000..d84f9950ce03d388ea9e06268105fdd504a44652 GIT binary patch literal 11528 zcmb`L^;Z`RNOvqtFP$PH z0wN;eK7P;h{0Yzea_`Lh&g5C8xL09+|3 zDE{@Ssi`3V06jfD1OQ=XW`+P<+1c6uCj$UnTwHv7e69cpKvot2fd~o;y8auMg#sW@ zQBhF<_W5IFRI z5<*i`6Z%j17j0DpaCL>t0(5kA;QtHzmjMBQA`J`-08kX-AI%l*3PG5dnEXROfBqce z>WYB4T3J~^pm13z65@)mv$J#k2XaM2p-|UZ_x3@PE3P89*P*8xMpC8)w zpYMu<$_4}kKoAI7IQAbG0>=gi2SX6(ckkXo5I8ve9~BuM9`1@jz~Rv7=x7820*Awq z@PB!52!tyN4oON%Lc*aaI23_EOh?P25s+!PY+70x7A}j0L(mALY zQP}G0YB=%|i9{Y0*&)!lX(SYjgrZO=Eb5;FN2Af_)leMT6^=z>kw`Qe*U`~&j)Yz! zq0!UlI5Yx2eSV38_Vo0?v6l!O8iz*r_xIz`|JH#U9v(*H5O_4=U^pCs!;g=TPfu6> zD;J5qL}4$dr>D_4-1#(odin&5#o}=PlH)J2@WsVN91eYnMP6c2)h8!79IpEO9FIdF z&(Co<{Km!x^70amN8@p5)aB*Q&d&77$-fRBhdVepK;!Y)^Yf#lBkbiR4v#)LIXOR{ zzP$W51NATB{QUg#5{tv*FE20gc=Yx4HU8g3JpTXf+5b;Q^naZHodp004@h7kbgATG z6*S0%SdBIm4a8FO>gDSA6{*VrNm;X>BTGh-xisS0by1~bp9D>-ZO2e$6B&}u^R8pNA8YFh26Y zY;6t6=6cLk+fAVBR$F~{(L-fwqVRjPZAa@9t@UR0G{c@L)KD5mVhN*K%+uW`DO{R7 z`r7L4sQrmdnU@=rR;ay^$8RLxH>yWOI{BN{*iUt|d|PUGnlG*Dqy=Y{djp9%&H{be z?2UYNVn}@9u}U;)0^cGAr%SovdW#}SQvym^nqF>BcX$5&!BW4A@6#ZqVwG{IRJHO` zMBn^A-JK}d3<;zbHtW$Wu)kBh!UIT_*8^ZFq)*t~5@&(*`dp4AjI@qtKvv%^=qOK$ zj{g{c_M5})SgshsojAU9!JU|os?yXP-h!4I#FeuGyNR-k9wmuQVnq(oOm1yOa8(i^ zz7#=qR2+9u=5HW9xR7A`Ly+Srw_t@?`*@nATh)HLZQ$4a3~O_;_y-v}C=l11+zO(^ zhv=^dIljXzb3!0Vn!IQhPeO271?=mW0@!<@gUpP|%)LlMCum<@f=;n+0kTbcqO{G- z^J`hATlLrS+(6H-rA=+9aiK!`csDk+2B9z2wdkXx#5d~rSrU#IRe!Ry8Xdi=imuC} zZ{?q~JH|8Jc4mi}z|BR6wK0&8_|mr1esalq^OxUSmfdQ;qt~27yvvHyJ9Iv(=}_<1 z9G2Idba>W902tl?oJ0g`CZBz6UO%Y$(X)R3>_ugT2M?V4O(sH=k=4xOThlC|se#nE zrs&xqy@l7=5VO1O*+B1=(u6P}uWnTPak|&}nBb>9aYn`sYYFjr_1d#Z*`->kk-r`O zaUZV*~&b)yE{mUWBTEl0##d)ERjpfb{Uu8biI` zm<-)dKBjIG#nW(ct7yXE(~jjcr@(?HZFWF!AC;3MJuyWQ=n15El_A-$_sX(p)V=54 zydK@rx79B`57q5364V{)%Xd)IE<`%ZALxvGD+2%#Tzmjx82Hh-G<&EHfD~95 z>$_YO$v&wn;IpEp!GmSf(JbO+BGA<}KhIv)T;Z2MjIo3`Q)w!@Kzg8&CWNM$ouY1% zN|sSCF@AY~N&s&n8GyR&CleVp3=}wqR}+uorIQp?BOBZ@3L@dUhcV#cZE39b3VhfYyqln(y#nxn?9z-xtH-iB9H_r8WrXHwYWpXJB+)0%rn#WNo1t5EvZyB ziiWnTE$l$%@DB@YHv!8yUmNe*{Wf zoHr1;ajjF)9Fb`{V>C)C&^>4cY(3$0+1a_~{x(P1ii$^}Q33MZTy1UPXTNkUT^YZS zK+eRNzgh{aT(#I}n8OBmrx<||k!27vJbV6gBxqX32K{2$>c}iN>G@9=5deU3d57%< zs3QB*xc(zT)q5?}<&WNqEYL?i!(2>w2CGq!(fr54gepA@(G>z;KZQkVgGlcaH9R%{ zZ-DZG2tWeJTBh218{2LmO4>p_sws{_1RgZz#~L1Io(8w0zwvRLfdFD%ii=HuhZ&qG z0d$(rah%HE@SINGp8-LzSiQgQ%fG+ zRd@WE7#&hmIFpv%H>+^>wX_p5@I2~up3U9n9FfQ%p5zxY)Xq1-ruAxcM3MZ3owrwH z4sPj0emKu3+ivx0;mgJ@s=L8{QD15G_w?KHu_=dvXLcJ=={?O=-rlMMG`WaAWM?5fByc#BtQBQULcb~NpX)P zR3&;ZpvL|Ckv$gt`D(=Y?|BaX_He|kGKN8#F-_#>NHSi|`FWAHim=6H2mST$8T{V@ z{IfoFVdYPJ&c2egk|Z9~62TE77PLZCS1@Yka9T(>y=6E}5PzxqgQZxm$A!Y@zr&@q z1dLRaU=-)G2imle&Z<^0XS&lbS1sa&J%Q*tbA^^^3 z1os^YXdMaWJqRNFl9n@*aG6P%wxo@#q==_USY}36)uew6*>T=RWQ1Z4;5-k@&fLam_4DegN(x@)T<<#06;tHv|DL0!`}+E z+jL(mQ|#o#1?=TK=I^{^3e~RZ)I@fBuO=fe%3elBeJztKbE+pe~}|`cHveUqS6o0l>OY zlDn`*y^y4%P$az2zqpWswdlcip&wxpZAcNfT9L0)5i_o6zp&_4e-UR$5!ca!(y!UP z*~J18f@*yPz?aw%W)pu(V*|6%x2785GT;VKJ%^R;HI#jN0I2IFBIF|H*d_&F zh`PSgIjELuEAr}el-nPfy#iC|fC2HRWk4=+$vrMo0Cl+oYdP0h*^|O@uj{gxN9CP^ z<-zfl`&;EIm=d230%2H1+I6|#HT<=8DLw#v|E+M9GZ+9=rR3-XM~{=`XIGM`R&l6N zslo_ndn-Al%A-?C*@;Ow{3=z1si=Fa-mzABUsrd0rK@4CF43!QyDnqbtm?N0)N>g; zNg-Fcu4&A!8v&D)WmgblK#e%^RmCb5aP9rGx;$`2XH{*_Xziz6u(KrRg?0U9Nc~lI z{dGtEEw29Wbv=Nsfl#S|*rs9kY3;xu@u*_mJci;K%!mzBxsRjiXDtVH>c?-D_b8TI z#UpElYxaE`*<}eyY)}tEQGz)r;ZD@<8L+3Vwh%yuXl^LGGV6u8cQuN>$=3^(YUsYl1wRdB`gcubZ3VYx3d=G&@^tS6l3FS{&KX z$~Rmu|72Khf#pA;Jv-4}t7xAav@ct$ymK?Gy84Mtg@!QA_3X2 zSKBpSoE3+Z%MzdDgcporO-JGWxQd@@56AH-ga33hbpTy!F^igz9Fl=pMHJJ%DJqf_u1F_`u_Bt6_MG&ig|1ccO6L*Qu@Vj`z7)0 z1JcR^vbF>AVFQrd0mZHX<+Xv>F>=^x@gv*3lm_sI(jb`Pvzy3go!mY>+syf_lEWIZ z-ETBllOcd!|0zv>g%A~!W2?OZWxuc;hZ$fJK)~^a_+SD|&P8mH0_d@(>a`)@X&?x^ zsiE{8`BF9Xqi?8?WtjeEh;y6NZ>`*qVssBf99`Qw8A=g6+3t5t8fV?*!ZrGRY~*Bn zq=0PH-)k(bV>F6=G;jiZd|e}+(oDxi{()k+%9g6Z1eBYYpUKXaU7MB5K2fAR8Y?;q z?;3@NjF;ZhJn!wws~y=G1m; z+U9Yr3W+mF=8Iy4W^Wh zZ|fUa@5w#yjVDT7oPMB><)H7Bqkkwz@(eb|ZpsGN#%8&Ig1sBqTp*T$WM_-WR2X1| zt1!u~7@P$6Om1^DmH}WyTfP+Hz9i72O=kT@`g~)nqapH*t>tc#KW3o6Nfj{BWx!S| zpU+OF^@eZawu)WVBk^slG7-&R8c6;kk@%)`PpRBAmh;i%x8w1K$<<6Ts=BUfKhb5v z6mUf14x`Vex0u!CdN z1#px+UGD#G$}{)yz3NocyLJ79)%P0>QibDWs)u|VC0U%cQYl9w--k+04i_g6qw{yS zxW=}qhv7b79rOd2Q6W4w?%*!9|o5GG!5&x(7Jwp5#sW&zJsHR~W`Rwm<=+yM? z`}OcgmBC@(_EWz3Po1>?{R*f00qr;{J1G-cA9JVvewTPMFHRaP`s0t$i3KTHVNj>S zOOmknV3k#pNiy1xTkzT7=40jkcJNjN`>=oA)OU(e)_Y$+O`SbAAv|XN!r3@p@pp(P z`D}W6J$&PML43qYeD*z82cGxo^g&oN6x_AqGx^b{ns#G+`|e!g^EmXFl)nIr7d_{9 z{JFo;5l{UyGWSBn_(B~&Lvc4$dN<8}Z;YdEYMb(BzxPFY*IJMPrCjom!H)~_?)_uE z%fOeHuv!WUauBano^rxGPxvWhl|=R%JZr8AEI~^#(?lgfOOp(w+x+EiJx=@m%=*V- z3QmnS{|q5fsmn^ZEV-hy*#X#^*ykcWOsy75zWm_uN9@&HiZP|s%vh9d`Hc&9@t7!B zX?w-GR`Ksl@r+uP;!;%C_2Sw%jNYN@rjco~tsX&*eMrL*q9L9KPOtQlh^K~qIGSVP z9Yqz!Mg1eFCUq1-YoPF$yEiWv^vq=>eP2G?*2Xf=<`&bCR#)A!I@*ZO2M$EfsEV8Xhgd@F^9G6fS2WDqIRD3FVC2u20_z< z>_WB07BvepQ7U`{PYcx5TWoxws*O2yTrT14=B3fL@Z%rdA^z$mK-8#QE9k>oI$>X| z@v#_VkjB7e=asP6#EY7pJFWs3%fSKPBf%9q(Nj zLDi2%f0NhYA>gN_gTaa2Sd-I;JCw8^6M$zY_#SpDQAgYX3dfUWm)L~}0EC@X0D`~F zM@DWj=caf=`HYf-Ok#stnc0U`PcI_0omBfWK3x!PfqeO%^EC|$M!%~6S}byb*_Z#` zjO9OLP2d5Fam@F}|0UL7H__6Xdpe*AZU(polFM3T2c;p+$qfU@j0-OJj9a(Os8ypV z8;WJExHDzwpT?gg#>Dyx(0+l7QK-z=T1;Ip^gt-Z2AA$&(Tj$F9yxuxK)Yh|70I$v zf5%A5#b-t0?nFPg=Cfz{SfWee%i9>ibW6QN4$jFUXIo!Ci4=y9KOQ{G4Xboc5I|lP zCA|z~DNfdiQP|D0kiT-veH&QmnExM@*mgAIUnj=GWNpP4Glfwea_*cj>21pt>uuNfy?d<;f&cu;@iwh-YrXRo!+gRF-m%kyHribG3945KHa|$I$wRp zpM?7KS~P|F4%~a9>^H>3WQg}2zAt?1HzrWg$`@k+ ztEMoYPvT~X&xEEP;xR7wz6Yd-04Vfp*h2Cc0HtzvKWD`xRDyIr<9f3__m@#uCC(fSm?9q08t$&mnfvMW=G{XOZrR3LfX zoeHadH~(5Cs?hBBXM43H!4v;YsjHD3T_I}W^CmK1K9mqskY|KcC6|kAw3@N2_hSh2 z0^u*GN3&VOZ9#t5Y<#qZwraPM-6J6>BE?O`nkRV!XHZA#{KiP=|p>-2eRFVhpFK{0K`nB<`g(X&z=ZQ<+|%=9qC?_x!9Y1 z6YR-MtL4?KmmC96>VAELAsVW&+*e#`6ECYz4;d_fo|UR@KR$O@^fNQ<4ZjH4T)#ll ze3fyu@Wh1&e|7}YMO-tBwWQ-YGLS*8j@S~~ znWDRbp;ps@=H6L$Yi=q^)>ux;=6E{q7tcGbBlG0_s$9LjTqjtUc&N}#L*733pREq8 zo40U4W;tl*Fy}z1@u>s- zKGG+gLA0Idw^$!}jIWC!a|h{@<$l;p$xY!fD3x;)mEEulAYyz@hvczodY~CgwS@5hW7;m#@~^?~|^KnjiVC#fP~(mI@|jM`J>_m)pbDm6@2p<}wv;Pm5LD(r_TUrQ6%%Cvb67y(K>nFW;2ShlxjIKLGQdB?}a8!A?2GZ?uK3(+pMnVJGCVnp1i*Ax84`{w(G6s z54+vaO^MsLwLtq*ng_QF0KfAA@<(TSp~F1Gvsc3o>T{_hVFz+;pM$60Zu~4DeRrt! zI0T!qalyCt5 z!18+2iE697$R2P;XhtFiAJ=M5C47#y94cubBo@_LglOpvc5}yTiI9=%o@yv&0!_&^ zqnSy~Zib>TEv6GBvG`LGGo|4eong}nRHa6XJB6Z|272FGI|ejts;}imuN^C>ZQ3~; zeKq`y{7JNjw&|x~f5^x){ShySoW0=4G`GAAEXp%s#J_Un#kUAg&A7}^w0N&tbRm%^ z8&Rwzk!KBU3^S4EKx7Patau?B@JU>Ux-SY7<;kWU);tnpN$g298gu%@U1T(ddGv+; zXf$N>MNM~%<>-r>h>!tU*|vw_oy4&wB&y79*3s=^;8ndg6$2*qm;RU`*kfF46^5$lg|L5LZN@AN%ibm2DJ3EG3GG zN{xyX?;7gEizw`{G+62AY9~wpZ+gRp*g9=x8*}UU%{UrfaI9AGt0)^V6H^YX@zN!J zG(0C?YfM0aKezK0?2C|oo^0M!&+qr(Gov(^n{|@KS%=b| zxDc7t1WlE;uBqQYHo1SPR`@%Z-XelanwI|NjP6ze%x%qu&nn3RaRqW0o_Q491s9T7 z7jC8Ufl-B&_j3Y&ZiDeTU(idOj1+P!oiQEuLz#w@74%JLRg# zx`mzy$XGU;r@xX%x1EO!Tp~5jBXwQOf5syXCXmsix{EU0R_dl_XPhc+PGo&7a zRIQ+zG}ol`0IQ}5tNydaEUiaA3$-~zR%g{#3n?r4>(vE@Nrep7l|0t-lDLw9>QauR zGFNMaKTdRerBcOW4q;iNQXedr=XEMwry^mWY1zQPijrGx(pqhPzS`otiVj$f_(ftL zWb;)xCbTQJHqT}kWw|Rs)$chU@SeUR4fA@4d?rFQN)XzKYot-1w|{`wM7baA0$ zrOd7yWf#@Ey2c@fq~v(z3*3Az=D4iAqqVX3+}{0}jQq=HQ^-b7SrgBY%-!m}#d)cv zLan}`_Cc=ZbRuEZeHnw*jn;R~ZcY|)bPikz?dNz`n@zR3i}i%~*Bx%J9plTSVGh#O7N-w=I%DM|@t?EAnPvn+_GJ*7%7S(#0*xgDtAd zt$QTfB-)YgC7Qc(c{Cqd>CLwp+_o9rps$?C^;X+$Y9Mc3Z?g?=voAt9%9mzXbaX)5 zT#P&14|aIucm5_t>m$Hg?(?E-9#_U!4@99ZW+3B8$=?(9dFYIma>=~uwjE#^cjQhbI zd!~%FGADM=H)^^m?b`!f`>I#1(EB#U!~3?2`*sKW_Lut(C!+PhgJxQr+M$R>4mTvB zeWIaTz23eXr}eY<;-)bN!)XVf+jl+Djmy!}o{Mgn;DcA3Rlba0Qh2`jr&qkzb`N^? zrN`#;@0UZDp^tfAexc{>LPNB=9it>UpUx`6{MfKT)H*P?cQs zp51nDPTXO{f!n807bep~j#Lc>15HL{0j67zAp_M&{7H^EvN4fFBj&GKG69QBsazRE z+L9zMT24BQgi}4NUj?py9v3dt-7oBgisiRTjnVKn1WDVHfAq`C7~aU&GM3njFYNtl z?g~^>An6za~{M1hnqn)>lO^Ux+IImH7(Z$=g%~s(jOYL z4>9{6brKuA&HrM~7i%%N(z5Xl)1;Nba2(JhVSlP2lQ{+>0>);NXfQ(xWOHhW+qS=t zqb9w=BlTmrCSXKS)An&QBoK{Wiy!gF;d8`chTh?!$1!bV;RDCxi(WBXW8rk((QV$C z>FpDN#?sT=hsE*iSy%=>$DrW(;oIX zW&ubjnaR}Oh_@UXf`|lK5=BYxIORG6e4p;VB5;rU%h?hrD`(I3f^vY+zK!^}$6@fK zRR1#OUV$dDg;<~0QJ#>&SZ-dO5g+q^pkWt-+;D?@U%ySb#ahS#EvX;<{krKlw7%Y- zF>mtIi})83{SexMEN?*zX8t07Cet#{;ev*vW@7%AXSy!F)8mE}f<_y%^uMhbq44`m znfJHN9>_{f-x?SZ^A!4fOcPie9UdE!;+?oQf__T95>{d{s=E4l!~Ic}24?1%x=umy zQ0UDmHFt@dSir_P=YzUmUGbN>aRU7VSM>KDj~8)xQr|~i6jK)q_g|zO&g5Jf3%jNg zw3rZy6+ioI%6wFG(>!DHn-W(2hD)7^0uiZa%=^TlF61)C^w4|e$V7>?l-bae*41=M zbyn@!QyTACd$5V?J<69E45}X~9(E_B3nus^!!royyz4wVx;%anBe*zZJ*D_X@HWjKvRR0}@`I zm(Zn=`OsRl0xA^C0{uNr=N-XYm_TpJ`UgV|=TG>8Q~J*jaNWLi8Xmoh^P$h?nK1%} z0cJYy_yx1fBI1L;_0GeuX^a8{F9a8gsTX2T|A1Mpb7%hOsgzus(U#YnFt41Y-d{`0 zXiVR%v05j0^ftG(6DsZqPNza`ngW5Wzqx(@|M9ow aUKpRL-?uZQcNnI-S%EvOLlO)CQ2IZCTi%xd literal 0 HcmV?d00001 diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index 26e10503..03d5effd 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -27,11 +27,20 @@ There are two flavors of this solution: the operation. This pattern is often called RELO (Resource based Long-running Operation). //image + +

    + The status monitor LRO flow +

    + 2. The returned resource is a new API resource called 'Stepwise Operation' and is created to track the status. This LRO solution is similar to the concept of Promises or Futures in other programming languages. //image - + +

    + The status monitor LRO flow +

    + RELO pattern is the preferred pattern for long running operations and should be used wherever possible. The pattern avoids complexity and consistent resource presentation makes things simpler for our users and tooling chain. From d41b4ec872458e7b399b65fbed54ed5e5dd42209 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 11 May 2022 14:10:33 -0500 Subject: [PATCH 372/729] Drop guidance on version discovery --- azure/Guidelines.md | 45 +-------------------------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a99869db..b89cc68c 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -3,6 +3,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2022-May-11 | Drop guidance on version discovery | | 2022-Mar-29 | Add guidelines about using durations | | 2022-Mar-25 | Update guideline for date values in headers to follow RFC 7231 | | 2022-Feb-01 | Updated error guidance | @@ -736,50 +737,6 @@ While removing a value from an enum is a breaking change, adding value to an enu > :ballot_box_with_check: **You SHOULD** use extensible enums unless you are positive that the symbol set will **NEVER** change over time. -#### Version Discovery - -Simpler clients may be hardcoded to a single version of a service. Since Azure services offer each version for a well-known period of time, a client that’s regularly maintained can be always operational without further complexity as long as during regular maintenance the client is moved forward to new versions in advance of older ones being retired. - -API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). -In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. - -:white_check_mark: **DO** support API version discovery, including - -1. Support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service - -2. Include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. - -3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it must include those versions (group and major.minor) in the `api-deprecated-versions` header. - -4. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. - -:ballot_box_with_check: **YOU SHOULD** support the following for version discovery: - -1. In addition to the functionality described here, services should support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. - -2. Services should allow unauthenticated HTTP `OPTIONS` requests. When doing so, authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. - -3. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request should return the group versions that are supported across the expanded set. - -Example request to discover API versions (blob storage container list API): - -```text -OPTIONS /?comp=list HTTP/1.1 -host: accountname.blob.core.azure.net -``` - -Example response: - -```text -200 OK -api-supported-versions: 2011-08,2012-02,1.1,2.0 -api-deprecated-versions: 2009-04,1.0 -Content-Length: 0 -``` - -Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. -In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. - ### Repeatability of requests The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. From c5f5989b2a1e55e824daa6bd7859d0be959a7687 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 11 May 2022 16:22:17 -0400 Subject: [PATCH 373/729] recources->resources --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 0bf5685b..2f9614f5 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -418,7 +418,7 @@ Recommended API Design patterns: | Evolvable Enums | The ability to extend enumerated types without breaking changes. | [Evolvable Enums](./patterns/evolvable-enums.md) | | Change Tracking | The ability to keep API consumers in sync with changes without polling. | [Change Tracking](./patterns/change-tracking.md) | | Namespace | The ability to organize resource definitions into a logical set. | [Namespace](./patterns/namespace.md) | -| Alternate Key | The ability to uniquely identify and query recources using an alternate key. | [Alternate Key](./patterns/alternate-key.md) | +| Alternate Key | The ability to uniquely identify and query resources using an alternate key. | [Alternate Key](./patterns/alternate-key.md) | | From 278f3fa8e184a7ac3cb50f1b50a765868169a9c1 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Thu, 12 May 2022 17:59:32 -0400 Subject: [PATCH 374/729] pattern name --- graph/patterns/longRunningOperations.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index 03d5effd..83a66aef 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -1,8 +1,8 @@ -# Pattern Name +# Long Running Operations Microsoft Graph API Design Pattern -### *The Long Running Operations (LRO) Pattern provides the ability to model operations where processing a client request takes a long time, but the client isn't blocked and can do some other work until operation completion.* +### *The Long Running Operations (LRO) pattern provides the ability to model operations where processing a client request takes a long time, but the client isn't blocked and can do some other work until operation completion.* ## Problem @@ -79,37 +79,37 @@ heuristics: ## Issues and Considerations -1. One or more clients MUST be able to monitor and operate on the same resource +- One or more clients MUST be able to monitor and operate on the same resource at the same time. -2. The state of the system SHOULD be always discoverable and testable. Clients +- The state of the system SHOULD be always discoverable and testable. Clients SHOULD be able to determine the system state even if the operation tracking resource is no longer active. Clients MAY issue a GET on some resource to determine the state of a long running operation -3. Long running operations SHOULD work for clients looking to "Fire and Forget" +- Long running operations SHOULD work for clients looking to "Fire and Forget" and for clients looking to actively monitor and act upon results. -4. Long running operations pattern may be supplemented by [Change Notification +- Long running operations pattern may be supplemented by [Change Notification pattern](change-notification.md) -5. Cancellation does not explicitly mean rollback. On a per API defined case it +- Cancellation does not explicitly mean rollback. On a per API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a canceled operation the API should return a consistent state which allows continued service. -6. A recommended minimum retention time for a stepwise operation is 24 hours. +- A recommended minimum retention time for a stepwise operation is 24 hours. Operations SHOULD transition to "tombstone" for an additional period of time prior to being purged from the system. -7. Services that provides a new operation resource MUST support GET semantics on the operation. +- Services that provides a new operation resource MUST support GET semantics on the operation. ## Examples -### Creating a new resource using RELO +### Create a new resource using RELO A client wants to provision a new database @@ -136,7 +136,7 @@ Content-Location: https://graph.microsoft.com/v1.0/databases/db1 } ``` -### Creating a new resource using Stepwise Operation: +### Create a new resource using Stepwise Operation: ``` POST https://graph.microsoft.com/v1.0/databases/ @@ -156,7 +156,7 @@ Location: https://graph.microsoft.com/v1.0/operations/123 ``` -### Polling on a Stepwise Operation: +### Poll on a Stepwise Operation: ``` From 5dc6c289ac6596a76752a5f6b90946250cb4e5f1 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Thu, 12 May 2022 15:57:15 -0700 Subject: [PATCH 375/729] Edited doc --- graph/patterns/longRunningOperations.md | 92 +++++++++++-------------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index 83a66aef..1d7d667f 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -1,38 +1,38 @@ -# Long Running Operations +# Long running operations Microsoft Graph API Design Pattern -### *The Long Running Operations (LRO) pattern provides the ability to model operations where processing a client request takes a long time, but the client isn't blocked and can do some other work until operation completion.* +__*The long running operations (LRO) pattern provides the ability to model operations where processing a client request takes a long time, but the client isn't blocked and can do some other work until operation completion.*__ ## Problem -The API design requires modeling operations on resources which takes long time +The API design requires modeling operations on resources, which takes a long time to complete so that API clients don't need to wait and can continue doing other -work while waiting for the final operation result. The client should be able to +work while waiting for the final operation results. The client should be able to monitor the progress of the operation and have an ability to cancel it if -needed.The API needs to provide a mechanism to track the work +needed. The API needs to provide a mechanism to track the work being done in the background. The mechanism needs to be expressed in the same -web style as other interactive APIs and support checking on the status and/or +web style as other interactive APIs. It also needs to support checking on the status and/or being notified asynchronously of the results. ## Solution -The solution is to model the API as a synchronous service which returns a -resource representing the eventual completion or failure of a long-running +The solution is to model the API as a synchronous service that returns a +resource that represents the eventual completion or failure of a long running operation. There are two flavors of this solution: -1. The returned resource is the targeted resource and includes the status of - the operation. This pattern is often called RELO (Resource based - Long-running Operation). +- The returned resource is the targeted resource and includes the status of + the operation. This pattern is often called RELO (resource-based + long running operation). //image

    The status monitor LRO flow

    -2. The returned resource is a new API resource called 'Stepwise Operation' and +- The returned resource is a new API resource called 'Stepwise Operation' and is created to track the status. This LRO solution is similar to the concept of Promises or Futures in other programming languages. //image @@ -41,61 +41,53 @@ There are two flavors of this solution: The status monitor LRO flow

    -RELO pattern is the preferred pattern for long running operations and should be -used wherever possible. The pattern avoids complexity and consistent resource +The RELO pattern is the preferred pattern for long running operations and should be +used wherever possible. The pattern avoids complexity, and consistent resource presentation makes things simpler for our users and tooling chain. -In general Microsoft Graph APIs guidelines for LRO follow [Microsoft REST API +In general, Microsoft Graph API guidelines for LRO follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#13-long-running-operations). -A single deviation from the base guidelines is that Microsoft Graph API -standards require using the following response headers: +A single deviation from the base guidelines is that Microsoft Graph API standards require that you use the following response headers: -- Content-Location header indicates location of a RELO resource. +- The Content-Location header indicates the location of a RELO resource. + - The API response says the targeted resource is being created by returning a 201 status code and the resource URI is provided in the Content-Location header, but the response indicates that the request is not completed by including "Provisioning" status. - - API response says the targeted resource is being created by returning 201 status code and the resource URI provided in the Content-Location header , but indicates the request is - not completed by including "Provisioning" status. +- The Location header indicates the location of a new stepwise operation LRO resource. + - The API response says the operation resource is being created at the URL provided in the Location header and indicates that the request is not completed by including a 202 status code. -- Location header indicates location of a new stepwise operation LRO resource. - - - API response says the operation resource is being created at the URL - provided in the Location header and indicates the request is - not completed by including a 202 status code. +## When to use this pattern -## When to Use this Pattern +Any API call that is expected to take longer than 1 second in the 99th percentile should use the long running operations pattern. - - Any API call that is expected to take longer than 1 seconds in the 99th percentile, should use Long-running Operations pattern. - -How to select which flavor of LRO pattern to use? API designer can follow these +How do you select which flavor of LRO pattern to use? An API designer can follow these heuristics: 1. If a service can create a resource with a minimal latency and continue updating its status according to the well-defined and stable state - transition model until completion then RELO model is the best choice. + transition model until completion, then the RELO model is the best choice. -2. Otherwise a service should follow the Stepwise Operation pattern.# +2. Otherwise, a service should follow the Stepwise Operation pattern. -## Issues and Considerations +## Issues and considerations -- One or more clients MUST be able to monitor and operate on the same resource - at the same time. +- One or more clients MUST be able to monitor and operate on the same resource at the same time. -- The state of the system SHOULD be always discoverable and testable. Clients +- The state of the system SHOULD always be discoverable and testable. Clients SHOULD be able to determine the system state even if the operation tracking resource is no longer active. Clients MAY issue a GET on some resource to - determine the state of a long running operation + determine the state of a long running operation. -- Long running operations SHOULD work for clients looking to "Fire and Forget" +- The long running operations pattern SHOULD work for clients looking to "fire and forget" and for clients looking to actively monitor and act upon results. -- Long running operations pattern may be supplemented by [Change Notification - pattern](change-notification.md) +- The long running operations pattern might be supplemented by the [change notification + pattern](change-notification.md). -- Cancellation does not explicitly mean rollback. On a per API defined case it - may mean rollback, or compensation, or completion, or partial completion, - etc. Following a canceled operation the API should return a consistent state which allows +- Cancellation of a long running operation does not explicitly mean a rollback. On a per API-defined case, it + might mean a rollback, or compensation, or completion, or partial completion, + etc. Following a canceled operation, the API should return a consistent state that allows continued service. - A recommended minimum retention time for a stepwise operation is 24 hours. @@ -122,7 +114,7 @@ POST https://graph.microsoft.com/v1.0/databases/ ``` The API responds synchronously that the database has been created and indicates -that provisioning operation is not fully completed by including the +that the provisioning operation is not fully completed by including the Operation-Location header and status property in the response payload. ``` @@ -136,7 +128,7 @@ Content-Location: https://graph.microsoft.com/v1.0/databases/db1 } ``` -### Create a new resource using Stepwise Operation: +### Create a new resource using the Stepwise Operation ``` POST https://graph.microsoft.com/v1.0/databases/ @@ -147,7 +139,7 @@ POST https://graph.microsoft.com/v1.0/databases/ ``` The API responds synchronously that the request has been accepted and includes -the Location header with an operation resource for further polling . +the Location header with an operation resource for further polling. ``` HTTP/1.1 202 Accepted @@ -156,14 +148,14 @@ Location: https://graph.microsoft.com/v1.0/operations/123 ``` -### Poll on a Stepwise Operation: +### Poll on a Stepwise Operation ``` GET https://graph.microsoft.com/v1.0/operations/123 ``` -Server responds that results are still not ready and optionally provides a +The server responds that results are still not ready and optionally provides a recommendation to wait 30 seconds. ``` @@ -175,7 +167,7 @@ Retry-After: 30 "status": "running" } ``` -Client waits the recommended 30 seconds and then invokes another request to get +The client waits the recommended 30 seconds and then invokes another request to get the results of the operation. ``` @@ -183,7 +175,7 @@ GET https://graph.microsoft.com/v1.0/operations/123 ``` -Server responds with a "status:succeeded" operation that includes the resource +The server responds with a "status:succeeded" operation that includes the resource location. ``` From d0f66a77c3dd28fc04f138869a2da1103a94d85e Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Thu, 12 May 2022 16:00:56 -0700 Subject: [PATCH 376/729] Fixed bullet --- graph/patterns/longRunningOperations.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index 1d7d667f..b49c7830 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -26,21 +26,23 @@ There are two flavors of this solution: - The returned resource is the targeted resource and includes the status of the operation. This pattern is often called RELO (resource-based long running operation). -//image +

    The status monitor LRO flow

    + - The returned resource is a new API resource called 'Stepwise Operation' and is created to track the status. This LRO solution is similar to the concept of Promises or Futures in other programming languages. -//image +

    The status monitor LRO flow

    + The RELO pattern is the preferred pattern for long running operations and should be used wherever possible. The pattern avoids complexity, and consistent resource presentation makes things simpler for our users and tooling chain. From 9aa4bbd6dfaad2e431ef62bcccc60eafd5081e15 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 17 May 2022 16:44:49 -0400 Subject: [PATCH 377/729] avoid tenant wide operations Co-authored-by: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> --- graph/patterns/longRunningOperations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index b49c7830..9a756fc0 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -146,7 +146,7 @@ the Location header with an operation resource for further polling. ``` HTTP/1.1 202 Accepted -Location: https://graph.microsoft.com/v1.0/operations/123 +Location: https://graph.microsoft.com/v1.0/databases/operations/123 ``` From 8a18f3648a39247ee0977b116eef4f82a5920c15 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 17 May 2022 20:42:36 -0400 Subject: [PATCH 378/729] clients to consumers Co-authored-by: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> --- graph/patterns/longRunningOperations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index 9a756fc0..f1fa0f1a 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -74,7 +74,7 @@ heuristics: ## Issues and considerations -- One or more clients MUST be able to monitor and operate on the same resource at the same time. +- One or more API consumers MUST be able to monitor and operate on the same resource at the same time. - The state of the system SHOULD always be discoverable and testable. Clients SHOULD be able to determine the system state even if the operation tracking From 75b484e578ed7db7da5ae1d99fc83a869064d934 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 17 May 2022 21:47:14 -0400 Subject: [PATCH 379/729] operation constraint --- graph/patterns/longRunningOperations.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index f1fa0f1a..3c76a706 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -50,13 +50,14 @@ presentation makes things simpler for our users and tooling chain. In general, Microsoft Graph API guidelines for LRO follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#13-long-running-operations). -A single deviation from the base guidelines is that Microsoft Graph API standards require that you use the following response headers: +There are some deviations from the base guidelines where Microsoft Graph API standards require that you do one of the following: -- The Content-Location header indicates the location of a RELO resource. +- For RELO pattern you should return the Content-Location header that indicates the location of the resource. - The API response says the targeted resource is being created by returning a 201 status code and the resource URI is provided in the Content-Location header, but the response indicates that the request is not completed by including "Provisioning" status. -- The Location header indicates the location of a new stepwise operation LRO resource. +- For LRO pattern you should return the Location header that indicates the location of a new stepwise operation resource. - The API response says the operation resource is being created at the URL provided in the Location header and indicates that the request is not completed by including a 202 status code. + - Microsoft Graph doesn’t allow tenant wide operation resources therefore stepwise operations is often modeled as a navigation property on the target resource. ## When to use this pattern From 543f87cb58b523664d4c58742c3ca834ab05d8a5 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Wed, 25 May 2022 14:47:36 -0700 Subject: [PATCH 380/729] Create subsets.md Moved *Modeling collection subsets* content from wiki page to GitHub --- graph/patterns/subsets.md | 177 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 graph/patterns/subsets.md diff --git a/graph/patterns/subsets.md b/graph/patterns/subsets.md new file mode 100644 index 00000000..b4a42dc7 --- /dev/null +++ b/graph/patterns/subsets.md @@ -0,0 +1,177 @@ +# Modeling collection subsets + +Microsoft Graph API Design Pattern + +The modeling collection subsets pattern is the modeling state associated to a collection that may include all instances, an included subset, an excluded subset, no instances, or any combinations of the preceding items. + +A common pattern is to apply a policy or state to a collection of objects. With this, there also comes the question of how to model cases where we want to apply to 'all' or 'none' without having to special case these values within the collection set or introduce cross-property dependencies. Likewise, we'd like to model it in a way where it is easy to understand and interpret usage from just looking at the schema. + +An example is where you have a policy that you need to be able to apply to users in an organization. You might want to support the default **None**, enablement for **All**, or enablement for **Select** users where you only grant it to a few users. + +## Problem + +-------- + +Existing patterns for this either have special-cased 'strings' or have tightly coupled dependencies between two independent properties. Neither is intuitive, both require reading documentation, and neither can be inferred from the schema or within client libraries. + +## Solution + +-------- + +Have an abstract base class where all 'variants' of the subset are derived types from the base subset. For more information, see the [general subtyping guidance](https://github.com/microsoft/api-guidelines/blob/graph/graph/patterns/subtypes.md). + +The abstract base class should also hold an enum for all possible variants. The purpose of including this is to allow for easier ways to do query and filter operations on variants like 'all' and 'none' without relying on `isof` functions. + +Base type + +```xml + + + + + + + + + + +``` + +Derived types + +```xml + + + + + + + + + + + +``` + +Be aware that the name values and types in the preceding examples are just examples and can be replaced with your scenario equivalent values. For example, types don't really need to be memberships. The collection doesn't have to be a collection at all; it can be singular and doesn't have to be a string. + +```xml + + + + + + + + + + + + + + + +``` + +## When to use this pattern + +-------- + +Use this pattern when supporting two or more collection states of the following, where at least one of the states is a subset variant: + +- All targets +- No targets +- Subset of targets to be included +- Subset of targets to be excluded + +If you only ever need to support two states—All or None—without using any subsets, it would be better to use a Boolean to toggle on and off. + +## Issues and considerations + +-------- + +Given that we are using an overarching subtype model, subtyping model limitations apply here as well; for more details, see the [subtyping documentation](https://github.com/microsoft/api-guidelines/blob/graph/graph/Modelling%20with%20Subtypes%20Pattern.md). + +## Example + +-------- + +GET https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/ + +_Note: unrelated properties on entities are omitted for easier readability_ + +```json +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#conditionalAccessPolicy", + "values": [ + { + "id": "66d36273-fe4c-d478-dc22-e0179d856ce7", + "conditions": { + "users": { + "includeGuestsOrExternalUsers": { + "externalTenants": { + "@odata.type":"microsoft.graph.conditionalAccessAllExternalTenants", + "membershipKind": "all" + } + } + } + } + }, + { + "id": "99d212f4-d94e-cde1-8e3c-208d78238277", + "conditions": { + "users": { + "includeGuestsOrExternalUsers": { + "externalTenants": { + "@odata.type":"microsoft.graph.conditionalAccessEnumeratedExternalTenants", + "membershipKind": "enumerated", + "members": ["bd005e2a-876d-4bf0-92a1-ae9ff4276d54"] + } + } + } + } + } + ] +} +``` + +POST https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/ + +_Note: unrelated properties on entities are omitted for easier readability_ + +```json +{ + "id": "66d36273-fe4c-d478-dc22-e0179d856ce7", + "conditions": { + "users": { + "includeGuestsOrExternalUsers": { + "externalTenants": { + "@odata.type":"microsoft.graph.conditionalAccessAllExternalTenants" + } + } + } + } +} +``` + +or + +POST https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/ + +_Note: unrelated properties on entities are omitted for easier readability_ + +```json +{ + "id": "66d36273-fe4c-d478-dc22-e0179d856ce7", + "conditions": { + "users": { + "includeGuestsOrExternalUsers": { + "externalTenants": { + "@odata.type":"microsoft.graph.conditionalAccessEnumeratedExternalTenants", + "members": ["bd005e2a-876d-4bf0-92a1-ae9ff4276d54"] + } + } + } + } +} +``` From f0d1a5aadbcb06886b6774fe98380cd6c5dac409 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Wed, 25 May 2022 17:55:53 -0700 Subject: [PATCH 381/729] Edited all patterns, including template --- graph/patterns/PatternDescriptionTemplate.md | 40 ++---- graph/patterns/alternate-key.md | 115 +++++++-------- graph/patterns/change-tracking.md | 54 ++++--- graph/patterns/dictionary-client-guidance.md | 22 +-- graph/patterns/dictionary.md | 75 +++++----- graph/patterns/evolvable-enums.md | 140 +++++++++---------- graph/patterns/facets.md | 32 ++--- graph/patterns/namespace.md | 100 ++++++------- graph/patterns/subtypes.md | 68 +++++---- 9 files changed, 292 insertions(+), 354 deletions(-) diff --git a/graph/patterns/PatternDescriptionTemplate.md b/graph/patterns/PatternDescriptionTemplate.md index 236aa2ff..26336e73 100644 --- a/graph/patterns/PatternDescriptionTemplate.md +++ b/graph/patterns/PatternDescriptionTemplate.md @@ -1,50 +1,30 @@ -# Pattern Name +# Pattern name Microsoft Graph API Design Pattern -  +*Provide a short description of the pattern.* -### *Provide a short description of the pattern.* - -
    ## Problem --------- -*Describe business context relevant for the pattern.* -*Provide a short description of the problem.* -* * +*Describe the business context relevant for the pattern.* + +*Provide a short description of the problem.* ## Solution --------- *Describe how to implement the solution to solve the problem.* -*Describe related patterns.* - -* * -## When to Use this Pattern ------------------------- +*Describe related patterns.* -*Describe when and why the solution is applicable and when it may not.* +## When to use this pattern -* * +*Describe when and why the solution is applicable and when it might not be.* -## Issues and Considerations -------------------------- +## Issues and considerations *Describe tradeoffs of the solution.* - -* * - ## Example -------- - -*Provide a short example from real life* - -* *  - - - +*Provide a short example from real life.* diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 80cbf03e..c4a49192 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -1,41 +1,34 @@ -# Alternate Key Pattern +# Alternate key Microsoft Graph API Design Pattern -_The Alternate Key Pattern provides the ability to query for a single, specific resource identifiable through an alternative set of properties that is not its primary key_ +*The alternate key pattern provides the ability to query for a single, specific resource identifiable through an alternative set of properties that is not its primary key.* ## Problem ---- +The resources exposed in Microsoft Graph are identified through a primary key, which guarantees uniqueness inside the same resource type. Often though, that same resource can also be uniquely identified by an alternative, more convenient property (or set of properties) that provides a better developer experience. -The resources exposed in Graph are identified through a Primary Key - which guarantees uniqueness inside the same resource type. Often though, that same resource can also be uniquely identified by an alternative, more convenient property (or set of properties) that provides a better developer experience. +Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also a unique property that could be used to identify it. -Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also an unique property that could be used to identify it. - -While it is still possible to use the `$filter` query parameter, such as - -`GET https://graph.microsoft.com/v1.0/users?$filter=mail eq 'bob@contoso.com'`, the returned result is wrapped in an array that needs to be unpacked. +While it is still possible to use the `$filter` query parameter, such as `GET https://graph.microsoft.com/v1.0/users?$filter=mail eq 'bob@contoso.com'`, the returned result is wrapped in an array that needs to be unpacked. ## Solution ---- +Resource addressing by using an alternative key can be achieved by using the same parentheses-style convention as the canonical key with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. -Resource addressing via an alternative key can be achieved using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. (Note: this is a hypothetical sample) +The following is a hypothetical sample: -https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0 -https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com` +https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0. -## When to Use this Pattern +https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com`. ---- +## When to use this pattern -This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides an useful alternative to the client. +This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides a useful alternative to the client. ## Example ---- - -The same user identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with specified key property name, and the canonical short form without key property name +The same user is identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with a specified key property name, and the canonical short form without a key property name. Declare `mail` and `ssn` as alternate keys on an entity: @@ -75,13 +68,41 @@ Declare `mail` and `ssn` as alternate keys on an entity: 1. Get a specific resource through `$filter`: -```http -GET https://graph.microsoft.com/v1.0/users/?$filter=ssn eq '123-45-6789' -``` + ```http + GET https://graph.microsoft.com/v1.0/users/?$filter=ssn eq '123-45-6789' + ``` + + ```json + { + "value": [ + { + "givenName": "Bob", + "jobTitle": "Retail Manager", + "mail": "bob@contoso.com", + "mobilePhone": "+1 425 555 0109", + "officeLocation": "18/2111", + "preferredLanguage": "en-US", + "surname": "Vance", + "userPrincipalName": "bob@contoso.com", + "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" + } + ] + } + ``` + +2. Get a specific resource either through its primary key or through the two alternate keys: + + ```http + GET https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 + GET https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') + GET https://graph.microsoft.com/v1.0/users(mail='bob@contoso.com') + ``` -```json -{ - "value": [ + > **Note:** When requesting a resource through its primary key, you might prefer to use `key-as-segment` (as shown earlier). Also, `key-as-segment` does not work for alternate keys. + + All three yield the same response: + + ```json { "givenName": "Bob", "jobTitle": "Retail Manager", @@ -89,45 +110,17 @@ GET https://graph.microsoft.com/v1.0/users/?$filter=ssn eq '123-45-6789' "mobilePhone": "+1 425 555 0109", "officeLocation": "18/2111", "preferredLanguage": "en-US", + "ssn": "123-45-6789", "surname": "Vance", "userPrincipalName": "bob@contoso.com", "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" } - ] -} -``` - -2. Get a specific resource either through its primary key, or through the two alternate keys: - -```http -GET https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 -GET https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') -GET https://graph.microsoft.com/v1.0/users(mail='bob@contoso.com') -``` + ``` -**NOTE:** When requesting a resource through its primary key you might want to prefer to use key-as-segment (as shown above). Also, the key-as-segment does not work for alternate keys. - -All of the 3 will yield the sare response: - -```json -{ - "givenName": "Bob", - "jobTitle": "Retail Manager", - "mail": "bob@contoso.com", - "mobilePhone": "+1 425 555 0109", - "officeLocation": "18/2111", - "preferredLanguage": "en-US", - "ssn": "123-45-6789", - "surname": "Vance", - "userPrincipalName": "bob@contoso.com", - "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" -} -``` - -3. Requesting a resource for an unsupported alternate key property +3. Request a resource for an unsupported alternate key property: -```http -GET https://graph.microsoft.com/v1.0/users(name='Bob') - -400 Bad Request -``` + ```http + GET https://graph.microsoft.com/v1.0/users(name='Bob') + + 400 Bad Request + ``` diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 0c33f5fb..2ac39056 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -1,59 +1,55 @@ -# Change Tracking +# Change tracking Microsoft Graph API Design Pattern *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.* ## Problem ---------- -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. +API consumers require an efficient way to keep data in sync with Microsoft Graph. The API design should be optimized to avoid polling because it is costly for consumers and producers alike and wouldn't guarantee data integrity. ## Solution --------- 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. -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. +This new endpoint can be used to sync API consumers. This is achieved through returning a delta link with a watermark. After the API consumer refreshes 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. -> 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. +> **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. -## Issues and Considerations -------------------------- +## When to use this pattern -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...) +Before using the change tracking pattern in your API definition, make sure that your scenario fits the following criteria: -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. +- API consumers want to sync the data. +- API consumers don't want to be immediately notified of changes (see the change notifications pattern for this scenario). +- API consumers are not looking for a "one-time" export or back-up mechanism. -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. -- The reason MUST be set to `changed` if the entity can be restored. `"@removed": {"reason": "changed"}`. -- The reason MUST be set to `deleted` if the entity cannot be restored. `"@removed": {"reason": "deleted"}`. +### Alternatives -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): +- Change notifications pattern (TODO add link when described) +- Backup pattern (TODO) -- When a user is added to the group `"members@delta": [{ "@odata.type": "#microsoft.graph.user", "id of the added user"}]` -- 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"}]` +## Issues and considerations -> 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. +The implementer MUST implement a watermark storage system in case of active watermarks. Passive watermarks are watermarks that can be retrieved from the context (for example, timestamp). Active watermarks represent information required to track the sync state, which cannot be retrieved from the context (such as a cursor from data store, partition affinity marker, partition ID, or generated unique sync identifier). -## When to Use this Pattern ------------------------- +The implementer MUST implement soft deletion for entities in the backend storage system. The soft deletion provides useful information to the client to appropriately reflect deletions. -Before using the change tracking pattern in your API definition, make sure your scenario fits the following criteria: +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. -- API consumers want to sync the data. -- API consumers don't want to be immediately notified of changes. (see change notifications pattern for this scenario). -- API consumers are not looking for a "one-time" export or back-up mechanism. +- The reason MUST be set to `changed` if the entity can be restored: `"@removed": {"reason": "changed"}` +- The reason MUST be set to `deleted` if the entity cannot be restored: `"@removed": {"reason": "deleted"}` -### Alternatives +When a link to an entity is deleted, when the linked entity is deleted, or when a link to an entity is added, the implementer MUST return a `property@delta` annotation. For example, considering the entity Group has a navigation property named members of type Collection(user): -- Change notifications pattern (TODO add link when described) -- Backup pattern (TODO) +- When a user is added to the group `"members@delta": [{ "@odata.type": "#microsoft.graph.user", "id of the added user"}]` +- When a user is removed from the group, or the target user is deleted from `"members@delta": [{"@removed": {"reason": "deleted"}, "id of the deleted or removed user"}]` + +> **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 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. However, expanding across workloads is not currently supported. ## Examples -------- -### Getting changes for the users entity set +### Get changes for the users entity set ```HTTP GET https://graph.microsoft.com/v1.0/users/delta @@ -94,7 +90,7 @@ GET https://graph.microsoft.com/v1.0/users/delta } ``` -> 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. +> **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 that the application can request right away to get the next page. ### CSDL example diff --git a/graph/patterns/dictionary-client-guidance.md b/graph/patterns/dictionary-client-guidance.md index 648f1385..966f3eab 100644 --- a/graph/patterns/dictionary-client-guidance.md +++ b/graph/patterns/dictionary-client-guidance.md @@ -1,16 +1,16 @@ # Dictionary types -Note: this document is to be moved into a central client guidance document in the future. +> **Note:** This document will be moved into a central client guidance document in the future. -The client guidance is a collection of additional information provided to SDK implementers and client applications. This information is meant to help understand how various guidelines and concept translate in their world and clarify a few unknowns. You should always read the corresponding guideline first to get a context understanding. +*The client guidance is a collection of additional information provided to SDK implementers and client applications. This information is meant to help understand how various guidelines and concepts translate in their world and clarify a few unknowns. Always read the corresponding guideline first to get a contextual understanding.* -[Read the guideline](./index.md). +[Read the guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md). ## OpenAPI example -The following json-schema/OpenAPI example defines a dictionary of which values will by of type **RoleSettings**. +The following json-schema/OpenAPI example defines a dictionary of which values are of type **RoleSettings**. -In **components** in **schemas**: +In **components** in **schemas**: ```json { @@ -38,13 +38,13 @@ In **components** in **schemas**: } ``` -## SDK Support +## SDK support -SDKs need to provide support for dictionary types so SDK consumers get a delightful development experience. Examples are provided below for different languages. Other aspects need to be taken into considerations: +SDKs need to provide support for dictionary types so that SDK consumers get a delightful development experience. Examples are provided for different languages. Other aspects need to be taken into consideration: -- Dictionaries support OData annotations (values prefixed with **@OData**), such annotations should not be inserted directly in the dictionary but rather in the additional properties manager. -- Dictionary types can inherit another dictionary type, this inheritance must be respected. -- Dictionary values can be of union types, if the target language doesn't support union types, a wrapper type should be generated as backward compatible solution with properties for each type of the union. +- Dictionaries support OData annotations (values prefixed with **@OData**); such annotations should not be inserted directly in the dictionary but rather in the additional properties manager. +- Dictionary types can inherit another dictionary type; this inheritance must be respected. +- Dictionary values can be of union types; if the target language doesn't support union types, a wrapper type should be generated as a backward compatible solution with properties for each type of the union. ### Dotnet @@ -74,6 +74,6 @@ or ## Request builder generation annotation -By default SDKs are not required to contain a set of request builders to run CRUD requests on entries in the dictionary. The dictionary will be updated as a whole by consumers by sending requests to the parent entity. +By default, SDKs are not required to contain a set of request builders to run CRUD requests on entries in the dictionary. The dictionary is updated as a whole by consumers by sending requests to the parent entity. If a **SupportedHttpMethod** annotation is specified for the dictionary type, request builders should be generated to allow consumers to automatically update the entries. diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 0a797d5d..a84fef89 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -2,53 +2,46 @@ Microsoft Graph API Design Pattern -*The Dictionary type provides the ability to create a set of primitives or objects, of the same type, where the API consumer can define a name for each value in the set.* +*The dictionary type provides the ability to create a set of primitives or objects of the same type where the API consumer can define a name for each value in the set.* ## Problem --------- -The API design requires a resource to include an unknown quantity of data elements of the same type that must be named using values provided by the API consumer. +The API design requires a resource to include an unknown quantity of data elements of the same type that must be named by using values provided by the API consumer. ## Solution --------- -API designers use a JSON object to represent a dictionary in a `application/json`response payload. When describing the model in CSDL, a new complex type can be created that derives from `Org.OData.Core.V1.Dictionary` and then use the `Org.OData.Validation.V1.OpenPropertyTypeConstraint`to constrain the type that can be used for the values in the dictionary. +API designers use a JSON object to represent a dictionary in an `application/json` response payload. When describing the model in CSDL, a new complex type can be created that derives from `Org.OData.Core.V1.Dictionary` and then uses the `Org.OData.Validation.V1.OpenPropertyTypeConstraint` to constrain the type that can be used for the values in the dictionary. -Dictionary entries can be added via `POST`, updated via `PATCH`, and they can be removed by setting the entry value to `null`. Multiple entries can be updated at once by using `PATCH` on the dictionary property. +Dictionary entries can be added via `POST`, updated via `PATCH`, and removed by setting the entry value to `null`. Multiple entries can be updated at the same time by using `PATCH` on the dictionary property. -## Issues and Considerations -------------------------- +## When to use this pattern -Dictionaries, sometimes called maps, are a collection of name-value pairs. They allow dynamic data sets to be accessed in a systematic manner and are a good compromise between a strictly defined ahead of time structure with all its named properties and between a loosely defined dynamic object (i.e. OData OpenTypes). +Before using a dictionary type in your API definition, make sure that your scenario fits the following criteria: -As dictionary entries are removed via setting the value to null, this means that dictionaries can only support values that are non-nullable. +- The data values MUST be related to one another semantically as a collection. +- The value types MUST be a primitive type or a **ComplexType**. Mixed primitive types are not allowed. +- The client MUST define the keys of this type, as opposed to the service defining them in advance. - OpenQuestions: - - Can/should PUT be supported on the dictionary property and/or the entry value - - What does OData say about being able to POST to a structured property? Will OData Web API allow that? - - Must an implementer support PATCH at both the dictionary level and the entry level? +### Alternatives -More information: +- [Open extensions](https://docs.microsoft.com/en-us/graph/extensibility-open-users) when you want to provide clients the ability to extend Microsoft Graph. +- [Complex types](https://docs.microsoft.com/en-us/odata/webapi/complextypewithnavigationproperty) when the set of data values are known. -- [OData reference](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md#dictionary) +## Issues and considerations -  -## When to Use this Pattern ------------------------- +Dictionaries, sometimes called maps, are a collection of name-value pairs. They allow dynamic data sets to be accessed in a systematic manner and are a good compromise between a strictly defined-ahead-of-time structure with all its named properties and a loosely defined dynamic object (such as OData OpenTypes). -Before using a dictionary type in your API definition make sure your scenario fits the following criteria: +Because dictionary entries are removed by setting the value to `null`, dictionaries can only support values that are non-nullable. -- The data values MUST be related to one another semantically as a collection. -- The value types MUST be a primitive type or is a **ComplexType**. Mixed primitive types are not allowed. -- The client MUST define the keys of this type. As opposed to the service defining them in advance. +Open questions: -### Alternatives +- Can/should PUT be supported on the dictionary property and/or the entry value? +- What does OData say about being able to POST to a structured property? Will OData Web API allow that? +- Must an implementer support PATCH at both the dictionary level and the entry level? -- [Open extensions](https://docs.microsoft.com/en-us/graph/extensibility-open-users) when you want to provide clients the ability to extend Microsoft Graph. -- [Complex types](https://docs.microsoft.com/en-us/odata/webapi/complextypewithnavigationproperty) when the set of data values are known. +For more information, see the [OData reference](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md#dictionary). ## Examples -------- ### JSON payload example @@ -70,9 +63,9 @@ The following example illustrates the resulting JSON for a property of dictionar ### HTTP calls examples -In this set of examples we're modeling a **roles** property of dictionary type on the user entity which is exposed by the users entity set. +In this set of examples, we model a **roles** property of dictionary type on the user entity, which is exposed by the users entity set. -#### Getting an entry from the dictionary +#### Get an entry from the dictionary ```HTTP GET https://graph.microsoft.com/v1.0/users/10/roles/author @@ -86,7 +79,7 @@ Response: } ``` -#### Getting the dictionary +#### Get the dictionary ```HTTP GET https://graph.microsoft.com/v1.0/users/10/roles @@ -108,7 +101,7 @@ Response: } ``` -#### Getting the entity with the dictionary +#### Get the entity with the dictionary ```HTTP GET https://graph.microsoft.com/v1.0/users/10 @@ -134,7 +127,7 @@ Response: } ``` -#### Creating an entry in the dictionary +#### Create an entry in the dictionary ```HTTP POST https://graph.microsoft.com/v1.0/users/10/roles/author @@ -144,7 +137,7 @@ POST https://graph.microsoft.com/v1.0/users/10/roles/author } ``` -#### Updating the dictionary +#### Update the dictionary ```HTTP PATCH https://graph.microsoft.com/v1.0/users/10/roles @@ -163,11 +156,13 @@ PATCH https://graph.microsoft.com/v1.0/users/10/roles } ``` -> Note: setting one of the keys to **null** deletes it from the dictionary. -> Note: the domain values for the existing author and maintainer entries will get updated. -> Note: the reviewer entry will be inserted in the dictionary. +> **Notes:** +> +> - Setting one of the keys to **null** deletes it from the dictionary. +> - The domain values for the existing author and maintainer entries are updated. +> - The reviewer entry is inserted in the dictionary. -#### Updating an entry in the dictionary +#### Update an entry in the dictionary ```HTTP PATCH https://graph.microsoft.com/v1.0/users/10/roles/author @@ -177,7 +172,7 @@ PATCH https://graph.microsoft.com/v1.0/users/10/roles/author } ``` -#### Deleting an entry from the dictionary +#### Delete an entry from the dictionary ```HTTP DELETE https://graph.microsoft.com/v1.0/users/10/roles/author @@ -212,6 +207,6 @@ The following example defines a complex type **roleSettings** as well as a dicti
    ``` -### Additional information +## See also -[SDK implementation guidance](./client-guidance.md). +- [SDK implementation guidance](./client-guidance.md) diff --git a/graph/patterns/evolvable-enums.md b/graph/patterns/evolvable-enums.md index 074aa42e..11d12918 100644 --- a/graph/patterns/evolvable-enums.md +++ b/graph/patterns/evolvable-enums.md @@ -1,4 +1,4 @@ -# Evolvable Enums +# Evolvable enums Microsoft Graph API Design Pattern @@ -6,83 +6,79 @@ Microsoft Graph API Design Pattern ## Problem ---- -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. +Frequently API producers want to add new members to an enum type after it is initially published. Some serialization libraries might fail when they encounter members in an enum type that were added after the serialization model was generated. In this documentation, we refer to any added enum members as unknown. ## Solution ---- -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 ```Prefer: 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-members``` HTTP header in the response. +The solution is to add a 'sentinel' member named `unknownFutureValue` at the end of the currently known enum members. The API producer then replaces any member that is numerically after `unknownFutureValue` with `unknownFutureValue`. -## When to Use this Pattern +If an API consumer can handle unknown enum values, the consumer can opt into receiving the unknown enum members by specifying the `Prefer: include-unknown-enum-members` HTTP header in their requests. The API producer then indicates that this preference has been applied by returning the `Preference-Applied: include-unknown-enum-members` HTTP header in the response. ---- +## When to use this pattern -- 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 require extending the enum. +It is a best practice to include an `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 that they have included all possible members in an enum, we still strongly recommend that you include an `unknownFutureValue` member to allow for unforeseen future circumstances that may require extending the enum. -- 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. +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. -## Issues and Considerations +## Issues and considerations ---- +Consider the following: -- 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. +- 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. -- Changing the value (i.e. position) of the ```unknownFutureValue``` sentinel member is considered a breaking change, and must follow the [deprecation](../deprecation.md) process. +- Changing the value (that is, position) of the `unknownFutureValue` sentinel member is considered a breaking change and must follow the [deprecation](../deprecation.md) process. -- 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. +- 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. -- 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. +- There is no ability for a client to indicate that it can handle a subset of unknown enum members. Instead, they can only specify that either they cannot handle any unknown enum members or they can handle any unknown enum members. -- The ```Prefer: 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. +- The `Prefer: 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. -- 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. +- 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 that 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 the next power of 2 value. -- 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. +- 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. -- If the value of a property containing a flag enum contains multiple unknown values they should all be replaced with a single ```unknownFutureValue``` value (i.e. there should not have multiple ```unknownFutureValue``` values returned). +- If the value of a property containing a flag enum contains multiple unknown values, they should all be replaced with a single `unknownFutureValue` value (that is, there should not be multiple `unknownFutureValue` values returned). -- 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. +- If an API consumer specifies `unknownFutureValue` for the value of a property in a `POST`/`PUT` request or as a parameter of an action or function, the API producer must reject the request with a `400 Bad Request` HTTP status. -- 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. +- 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 (that is, the existing value should not be changed). In the case where the API producer treats `PATCH` as an upsert, the call MUST be rejected with a `400 Bad Request` HTTP status. -- If an API consumer specifies an enum member greater than ```unknownFutureValue``` in any request without specifying the ```Prefer: include-unknown-enum-members``` header the API producer must reject the request with a ```400 Bad Request``` HTTP status. +- If an API consumer specifies an enum member greater than `unknownFutureValue` in any request without specifying the `Prefer: include-unknown-enum-members` header, the API producer must reject the request with a `400 Bad Request` HTTP status. -- For details of how the ```unknownFutureValue``` value is handled as part of a ```$filter``` clause please consult the following examples: +- For details about how the `unknownFutureValue` value is handled as part of a `$filter` clause, consult the following examples: -### CSDL + - **CSDL** + + ```xml + + + + + + + + + + + ``` + + - **Filter behavior** + + | `$filter` clause | `Prefer: include-unknown-enum-members` Absent | `Prefer: include-unknown-enum-members` Present | + |---|---|---| + | `enumProperty eq unknownFutureValue`| Return entities where enumProperty has any value greater than `unknownFutureValue` replacing actual value with `unknownFutureValue`| Return nothing | + | `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` | + | `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`| + | `enumProperty eq newValue` | `400 Bad Request` | Return entities where enumProperty has the value `newValue` | + | `enumProperty gt newValue` | `400 Bad Request` | Return entities where enumProperty has a value greater than `newValue` | + | `enumProperty lt newValue` | `400 Bad Request` | Return entities where enumProperty has a value less than `newValue` | -```xml - - - - - - - - - - -``` - -### Filter Behavior - -| ```$filter``` clause | ```Prefer: include-unknown-enum-members``` Absent | ```Prefer: include-unknown-enum-members``` Present | -|---|---|---| -| ```enumProperty eq unknownFutureValue```| Return entities where enumProperty has any value greater than ```unknownFutureValue``` replacing actual value with ```unknownFutureValue```| Return nothing | -| ```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``` | -| ```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```| -| ```enumProperty eq newValue``` | ```400 Bad Request``` | Return entities where enumProperty has the value ```newValue``` | -| ```enumProperty gt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value greater than ```newValue``` | -| ```enumProperty lt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value less than ```newValue``` | - -- 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 ```Prefer: include-unknown-enum-members``` header is absent. +- 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 `Prefer: include-unknown-enum-members` header is absent. ## Examples ---- - -For the following examples we will consider the ```managedDevice``` entity which refers to the ```managedDeviceArchitecture``` enum type. +For the following examples, we consider the `managedDevice` entity, which refers to the `managedDeviceArchitecture` enum type. ```xml @@ -92,7 +88,7 @@ For the following examples we will consider the ```managedDevice``` entity which
    ``` -When the ```managedDeviceArchitecture``` enum was initially published to Graph it was defined as below: +When the `managedDeviceArchitecture` enum was initially published to Microsoft Graph, it was defined as follows: ```xml @@ -106,7 +102,7 @@ When the ```managedDeviceArchitecture``` enum was initially published to Graph i ``` -The enum was later extended to add a new value of ```quantum``` leading to the below CSDL +The enum was later extended to add a new value of `quantum`, leading to the following CSDL: ```xml @@ -120,7 +116,7 @@ The enum was later extended to add a new value of ```quantum``` leading to the b ``` -### Default Behavior +### Default behavior ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture @@ -148,9 +144,9 @@ GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=dis } ``` -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``` +In this case, the value of the `processorArchitecture` property is `quantum`. However, because the client did not request the `include-unknown-enum-members` header, the value was replaced with `unknownFutureValue`. -### Include opt-in Header +### Include opt-in header ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture @@ -179,7 +175,7 @@ Preference-Applied: include-unknown-enum-members } ``` -### Default Sort Behavior +### Default sort behavior ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$orderBy=processorArchitecture @@ -204,7 +200,7 @@ GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=dis } ``` -### Sort Behavior with opt-in Header +### Sort behavior with opt-in header ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture @@ -233,7 +229,7 @@ Preference-Applied: include-unknown-enum-members } ``` -### Default Filter Behavior +### Default filter behavior ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$filter=processorArchitecture gt x64 @@ -254,7 +250,7 @@ GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=dis } ``` -### Filter Behavior with opt-in Header +### Filter behavior with opt-in header ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$filter=processorArchitecture gt x64 @@ -279,7 +275,7 @@ Preference-Applied: include-unknown-enum-members } ``` -### Patch Example +### Patch example ```http PATCH https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/1 @@ -313,9 +309,9 @@ Preference-Applied: include-unknown-enum-members } ``` -## Flag Enum Examples +## Flag enum examples -For the following examples we will consider the ```windowsUniversalAppX``` entity which refers to the ```windowsArchitecture``` flag enum type. +For the following examples, we consider the `windowsUniversalAppX` entity, which refers to the `windowsArchitecture` flag enum type. ```xml @@ -325,7 +321,7 @@ For the following examples we will consider the ```windowsUniversalAppX``` entit
    ``` -When the ```windowsArchitecture``` enum was initially published to Graph it was defined as below: +When the `windowsArchitecture` enum was initially published to Microsoft Graph, it was defined as follows: ```xml @@ -339,7 +335,7 @@ When the ```windowsArchitecture``` enum was initially published to Graph it was ``` -The enum was later extended to add a new value of ```quantum``` leading to the below CSDL +The enum was later extended to add a new value of `quantum`, leading to the following CSDL: ```xml @@ -353,7 +349,7 @@ The enum was later extended to add a new value of ```quantum``` leading to the b ``` -### Flag Enum Default Behavior +### Flag enum default behavior ```http GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures @@ -381,9 +377,9 @@ GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=disp } ``` -In this case the value of the ```applicableArchitectures``` property includes ```quantum``` however since the client did not request the ```include-unknown-enum-members``` header the value was replaced with ```unknownFutureValue``` +In this case, the value of the `applicableArchitectures` property includes `quantum`. However, because the client did not request the `include-unknown-enum-members` header, the value was replaced with `unknownFutureValue`. -### Flag Enum Include opt-in Header +### Flag enum include opt-in header ```http GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures @@ -415,7 +411,7 @@ Preference-Applied: include-unknown-enum-members } ``` -### Flag Enum Default Filter Behavior +### Flag enum default filter behavior ```http GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures&$filter=applicableArchitectures has unknownFutureValue @@ -438,7 +434,7 @@ GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=disp } ``` -### Flag Enum Include opt-in Header Filter Behavior +### Flag enum include opt-in header filter behavior ```http GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures&$filter=applicableArchitectures has unknownFutureValue @@ -454,7 +450,7 @@ Preference-Applied: include-unknown-enum-members } ``` -### Flag Enum Patch Example +### Flag enum patch example ```http PATCH https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps/1 diff --git a/graph/patterns/facets.md b/graph/patterns/facets.md index 2ed85ccd..da232342 100644 --- a/graph/patterns/facets.md +++ b/graph/patterns/facets.md @@ -1,35 +1,33 @@ -# Facets Pattern +# Facets Microsoft Graph API Design Pattern -### *A frequent pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with common properties and facets for variants.* - +*A frequent pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with common properties and facets for variants.* ## Problem -API designer needs to model a set of heterogeneous resources that have common properties and behaviors, and may express features of multiple variants at a time because variants are not mutually exclusive. -For example a movie clip stored on OneDrive is both a file and a video. There are properties associated to each variant. + +An API designer needs to model a set of heterogeneous resources that have common properties and behaviors and might express features of multiple variants at a time because variants are not mutually exclusive. +For example, a movie clip stored on OneDrive is both a file and a video. There are properties associated to each variant. ## Solution -API designers create multiple complex types to bundle properties for each variant then define an entity type with a property for each complex type to hold the properties of the variant. -In this solution a child variant is identified by a presence of one or more facets in the parent object. +API designers create multiple complex types to bundle properties for each variant, and then define an entity type with a property for each complex type to hold the properties of the variant. -## Issues and Considerations +In this solution, a child variant is identified by the presence of one or more facets in the parent object. -When introducing a new facet, you need to ensure that the new facet doesn't change the semantic of the model with it's implicit constraints. +## When to use this pattern +The facet pattern is useful when there is a number of variants and they are not mutually exclusive. It also makes it syntactically easier to query resources by using OData $filter expression because it doesn't require casting. -## When to Use this Pattern +You can consider related patterns such as [Type hierarchy](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat bag of properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). -The facet pattern is useful when there is a number of variants and they are not mutually exclusive. It also makes syntactically easier to query resources using OData $filter expression since it doesn't require casting. +## Issues and considerations -There are related patterns to consider such as -[Type Hierarchy](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat -bag of -properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). +When introducing a new facet, you need to ensure that the new facet doesn't change the semantic of the model with its implicit constraints. ## Example -The driveItem resource represents a file, folder,image or other item stored in a drive and is modeled using entity type with multiple facets. + +The driveItem resource represents a file, folder, image, or other item stored in a drive and is modeled by using entity type with multiple facets. ```XML @@ -64,7 +62,7 @@ The driveItem resource represents a file, folder,image or other item stored in a
    ``` -API request to get all items from a personal OneDrive will return a heterogenous collection with different facets populated. In the example below there is a folder, a file and an image in the collection. The image entity has two facets populated: file and image. +An API request to get all items from a personal OneDrive returns a heterogenous collection with different facets populated. In the following example, there is a folder, a file, and an image in the collection. The image entity has two facets populated: file and image. ``` https://graph.microsoft.com/v1.0/me/drive/root/children diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index aad80e78..841e5bc7 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -2,104 +2,88 @@ Microsoft Graph API Design Pattern -*The Namespace provides the ability to organize resource definitions together into a logical set.* +*The namespace pattern provides the ability to organize resource definitions together into a logical set.* ## Problem ---- -When building a complex offering API designers may need to model many different -resources and their relationships. For better user experience and -discoverability related API elements need to be grouped together. - +When building a complex offering, API designers might need to model many different +resources and their relationships. For a better user experience and +discoverability, related API elements need to be grouped together. ## Solution ---- -API designers can use the Namespace attribute of the CSDL schema to declare a -namespace and logically organize related API entities in the Graph metadata. +API designers can use the namespace attribute of the CSDL schema to declare a +namespace and logically organize related API entities in the Microsoft Graph metadata. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ XML +```XML ... -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` -A public namespace must have "microsoft.graph." prefix and be presented in camel -case, i.e microsoft.graph.myNamespace. +A public namespace must contain the `microsoft.graph.` prefix and be presented in camel +case; that is, `microsoft.graph.myNamespace`. -When type casting is required in the API query, request or response, a fully +When type casting is required in the API query, request, or response, a fully qualified type name is represented as concatenation of a namespace and a type -name. For consistent user experience namespaces MUST be aligned with the corresponding API category path segment. - - -## When to Use this Pattern +name. For a consistent user experience, namespaces MUST be aligned with the corresponding API category path segment. ---- -API resource grouping creates a user-friendly experience keeping all resources -for a specific feature close together -and limits the length of IDE prompts such as auto-complete in some programming languages. +## When to use this pattern -We recommend that a new namespace should be aligned with top-level API category. +API resource grouping creates a user-friendly experience, keeping all resources for a specific feature close together and limiting the length of IDE prompts such as auto-complete in some programming languages. -## Issues and Considerations +We recommend that a new namespace be aligned with a top-level API category. ---- -1. Microsoft Graph consistency requirements discourage using the same type - names for different concepts even within different namespaces. Microsoft - Graph type names must be descriptive and unique within the API - surface without requiring full qualification. +## Issues and considerations -2. Namespace must be consistent with API category in the navigation path according to [Microsoft Graph REST API Guidelines](GuidelinesGraph.md#uniform-resource-locators-urls) +- Microsoft Graph consistency requirements discourage using the same type names for different concepts even within different namespaces. Microsoft Graph type names must be descriptive and unique within the API surface without requiring full qualification. -3. When type name is ambiguous and requires a namespace qualifier, changing - namespace is a breaking change. +- A namespace must be consistent with an API category in the navigation path according to [Microsoft Graph REST API Guidelines](GuidelinesGraph.md#uniform-resource-locators-urls). -4. To extend a type in a different schema, a service must declare that schema - and the type in it. This is conceptually similar to .NET partial types. +- When type name is ambiguous and requires a namespace qualifier, changing a namespace is a breaking change. -5. To reference a type in a different schema, simply refer to that type by - fully qualified name (namespace + type name). +- To extend a type in a different schema, a service must declare that schema and the type in it. This is conceptually similar to .NET partial types. -6. Cyclical references between namespaces are not allowed as many - object-oriented languages don’t support a cycles between namespaces. +- To reference a type in a different schema, simply refer to that type by its fully qualified name (namespace + type name). -7. Microsoft Graph has some predefined constraints for declared namespaces: +- Cyclical references between namespaces are not allowed because many object-oriented languages don’t support cycles between namespaces. - - All public namespaces must have a prefix "microsoft.graph" +- Microsoft Graph has some predefined constraints for declared namespaces: - - Only one level of nesting deeper then "microsoft.graph" is supported - - - If a namespace does not begin with "microsoft.graph" prefix, all types - in the schema will be coerced into the main "microsoft.graph" namespace. + - All public namespaces must have the prefix `microsoft.graph`. + + - Only one level of nesting deeper than `microsoft.graph` is supported. + + - If a namespace does not begin with the `microsoft.graph` prefix, all types in the schema are coerced into the main `microsoft.graph` namespace. ## Examples ---- -### Namespace and type declarations: +### Namespace and type declarations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ XML +```XML ”\> … -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` + +Fully qualified type name: `microsoft.graph.search.bookmark` -Fully qualified type name: "microsoft.graph.search.bookmark" +### Managing multiple schemas -### Managing multiple schemas: +Workloads must define schemas in their CSDL by using the Edmx format. +Following is an example of a workload that exposes multiple namespaces. -Workloads must define schemas in their csdl using the Edmx format. -Here is an example of a workload that exposes multiple namespaces. +> **Tip:** As with schemas that exist in the `microsoft.graph` namespace, defining an +entity type is optional; by default your schema derives all entity types +from `microsoft.graph.entity`. -**Tip:** As with schemas that exist in the microsoft.graph namespace, defining an -entity type is optional, by default your schema derives all entity types -from microsoft.graph.entity. +> **Warning:** Do not deviate from the general structure in the following example. +The schema validation tool expects the XML structure (including XML namespace +declarations) to match this example. -**Warning:** Do not deviate from the general structure in the example below. -Schema validation tool expects the XML structure (including xml namespace -declarations) to match the example below. ```XML diff --git a/graph/patterns/subtypes.md b/graph/patterns/subtypes.md index 73dea0a2..eff09726 100644 --- a/graph/patterns/subtypes.md +++ b/graph/patterns/subtypes.md @@ -1,57 +1,51 @@ -# Type Hierarchy +# Type hierarchy Microsoft Graph API Design Pattern - -### *A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This allows us to model collections of objects that have slightly different metadata and behavior.* +*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This lets us model collections of objects that have slightly different metadata and behavior.* ## Problem -The API design requires to model a set of entities based on a common concept -that can be further grouped into **mutually exclusive variants** with specific -properties and behaviors. The API design should be evolvable and allow addition +The API design requires that we model a set of entities based on a common concept +that can be further grouped into *mutually exclusive variants* with specific +properties and behaviors. The API design should be evolvable and allow the addition of new variants without breaking changes. ## Solution -API designers can use OData **type hierarchy**, where there is one abstract base +API designers can use OData *type hierarchy*, where there is one abstract base type with a few shared properties representing the common concept and one -sub-type for each variant of the entity. In hierarchy, the interdependencies of properties, i.e. which properties are relevant for which variants, is fully captured in metadata and client code can potentially leverage that to construct and/or validate requests. - -## Issues and Considerations +subtype for each variant of the entity. In the hierarchy, the interdependencies of properties, that is, which properties are relevant for which variants, is fully captured in metadata, and client code can potentially leverage that to construct and/or validate requests. -When introducing a new subtype to the hierarchy, developers need to ensure that -the new subtype doesn't change the semantic of the type hierarchy with its -implicit constraints. -To retrieve properties specific for a derived type an API request URL may need to include casting to the derived type. If type hierarchy is very deep then resulting URL may become very long and not easily readable. +## When to use this pattern -There are a few consideration to take into account when new sub-types -are introduced: +The type hierarchy pattern is well suited to a use case where each variant of a +common concept has unique properties and behaviors, no combination of variants +is anticipated, and API queries are managed programmatically with type casting. -- TODO add something about SDK dependencies and required actions -- TODO Client libraries for strongly typed language might ignore some of the values - in the @odata.type property without further configuration and need to be - updated to be able to pick the right (client) type to deserialize into. -- In the case of public APIs in GA versions clients may develop their applications to support exclusively the current set of subtypes and don’t expect new variations. To mitigate the risk of clients disruption, when introducing a new subtype, allow ample time for communication and rollout. +You can consider related patterns such as +[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat bag of properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). +## Issues and considerations +When introducing a new subtype to the hierarchy, developers need to ensure that +the new subtype doesn't change the semantic of the type hierarchy with its implicit constraints. -## When to Use this Pattern +To retrieve properties specific for a derived type, an API request URL might need to include casting to the derived type. If the type hierarchy is very deep, then the resulting URL might become very long and not easily readable. -The Type hierarchy pattern is well suited to use case where each variant of a -common concept has unique properties and behaviors, no combinations of variants -is anticipated, API queries are managed programmatically with type casting. +There are a few considerations to take into account when new subtypes are introduced: -There are related patterns to consider such as -[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat -bag of -properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). +- *TODO add something about SDK dependencies and required actions* +- *TODO* Client libraries for a strongly typed language might ignore some of the values + in the @odata.type property without further configuration and need to be + updated to be able to pick the right (client) type to deserialize into. +- In the case of public APIs in GA versions, clients might develop their applications to support exclusively the current set of subtypes, and don’t expect new variations. To mitigate the risk of clients' disruption, when introducing a new subtype, allow ample time for communication and rollout. ## Example The directoryObject type is the main abstraction for many directory -types such as users, organizational contacts, devices, service principals -and groups stored in Azure Active Directory. Since any a directoryObject object is a unique entity, the directoryObject type itself is derived from the graph.entity base type. +types such as users, organizational contacts, devices, service principals, +and groups stored in Azure Active Directory. Because any directoryObject object is a unique entity, the directoryObject type itself is derived from the `graph.entity` base type. ```XML @@ -65,8 +59,7 @@ and groups stored in Azure Active Directory. Since any a directoryObject object ``` - -Groups and users are derived types and modeled as +Groups and users are derived types and modeled as follows: ```XML   @@ -79,7 +72,7 @@ Groups and users are derived types and modeled as ``` -API request to get members of a group returns a heterogeneous collection of +An API request to get members of a group returns a heterogeneous collection of users and groups where each element can be a user or a group, and has an additional property @odata.type for a variant subtype: @@ -106,7 +99,8 @@ Response payload shortened for readability:     ] } ``` -API request for a subtype specific property requires type casting to the subtype, i.e. to retrieve jobTitle property, enabled for the user type, you need to cast from the directoryObject collection items to the microsoft.graph.group derived type. + +An API request for a subtype-specific property requires type casting to the subtype; that is, to retrieve the jobTitle property enabled for the user type, you need to cast from the directoryObject collection items to the `microsoft.graph.group` derived type. ``` GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members/microsoft.graph.user?$select=displayName,jobTitle @@ -124,7 +118,9 @@ Response payload shortened for readability: ] } ``` -API request to create a subtype object in a polymorphic collection requires "@odata.type" specified in the request body. + +An API request to create a subtype object in a polymorphic collection requires "@odata.type" specified in the request body. + ``` POST https://graph.microsoft.com/v1.0/directoryObjects From bf5bb78f8fc31536ad509853ceaa965c6470dd60 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Wed, 25 May 2022 18:07:15 -0700 Subject: [PATCH 382/729] More edits to this page --- graph/patterns/subsets.md | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/graph/patterns/subsets.md b/graph/patterns/subsets.md index b4a42dc7..bf535019 100644 --- a/graph/patterns/subsets.md +++ b/graph/patterns/subsets.md @@ -2,27 +2,23 @@ Microsoft Graph API Design Pattern -The modeling collection subsets pattern is the modeling state associated to a collection that may include all instances, an included subset, an excluded subset, no instances, or any combinations of the preceding items. +*The modeling collection subsets pattern is the modeling state associated to a collection that may include all instances, an included subset, an excluded subset, no instances, or any combinations of the preceding items.* + +## Problem A common pattern is to apply a policy or state to a collection of objects. With this, there also comes the question of how to model cases where we want to apply to 'all' or 'none' without having to special case these values within the collection set or introduce cross-property dependencies. Likewise, we'd like to model it in a way where it is easy to understand and interpret usage from just looking at the schema. An example is where you have a policy that you need to be able to apply to users in an organization. You might want to support the default **None**, enablement for **All**, or enablement for **Select** users where you only grant it to a few users. -## Problem - --------- - Existing patterns for this either have special-cased 'strings' or have tightly coupled dependencies between two independent properties. Neither is intuitive, both require reading documentation, and neither can be inferred from the schema or within client libraries. ## Solution --------- - Have an abstract base class where all 'variants' of the subset are derived types from the base subset. For more information, see the [general subtyping guidance](https://github.com/microsoft/api-guidelines/blob/graph/graph/patterns/subtypes.md). The abstract base class should also hold an enum for all possible variants. The purpose of including this is to allow for easier ways to do query and filter operations on variants like 'all' and 'none' without relying on `isof` functions. -Base type +**Base type** ```xml @@ -37,7 +33,7 @@ Base type ``` -Derived types +**Derived types** ```xml @@ -75,8 +71,6 @@ Be aware that the name values and types in the preceding examples are just examp ## When to use this pattern --------- - Use this pattern when supporting two or more collection states of the following, where at least one of the states is a subset variant: - All targets @@ -88,17 +82,15 @@ If you only ever need to support two states—All or None—without usin ## Issues and considerations --------- - Given that we are using an overarching subtype model, subtyping model limitations apply here as well; for more details, see the [subtyping documentation](https://github.com/microsoft/api-guidelines/blob/graph/graph/Modelling%20with%20Subtypes%20Pattern.md). ## Example --------- - +```http GET https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/ +``` -_Note: unrelated properties on entities are omitted for easier readability_ +_Note: Unrelated properties on entities are omitted for easier readability._ ```json { @@ -135,9 +127,11 @@ _Note: unrelated properties on entities are omitted for easier readability_ } ``` +```http POST https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/ +``` -_Note: unrelated properties on entities are omitted for easier readability_ +_Note: Unrelated properties on entities are omitted for easier readability._ ```json { @@ -156,9 +150,11 @@ _Note: unrelated properties on entities are omitted for easier readability_ or +```http POST https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/ +``` -_Note: unrelated properties on entities are omitted for easier readability_ +_Note: Unrelated properties on entities are omitted for easier readability._ ```json { From 97a8eea7ab4f37086c9beb9d80a9e3c3ca002dc5 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Thu, 26 May 2022 13:30:49 -0700 Subject: [PATCH 383/729] Improved alternate key pattern content. Updated the content for clarity, directness, minor corrections on the scope of key uniqueness, fuller error examples, and being specific with guidance against multi-part alternate keys. Also introduced the concept of key parentheses for those Graph users who've never encountered them, given slashes are the known public format. --- graph/patterns/alternate-key.md | 66 +++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index c4a49192..3754e702 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -2,29 +2,51 @@ Microsoft Graph API Design Pattern -*The alternate key pattern provides the ability to query for a single, specific resource identifiable through an alternative set of properties that is not its primary key.* +*The alternate key pattern provides the ability to query for a single, specific resource identifiable via one of an alternative set of properties that is not its primary key.* ## Problem -The resources exposed in Microsoft Graph are identified through a primary key, which guarantees uniqueness inside the same resource type. Often though, that same resource can also be uniquely identified by an alternative, more convenient property (or set of properties) that provides a better developer experience. +The resources exposed in Microsoft Graph are identified through a primary key, which guarantees uniqueness inside the same resource collection. Often though, that same resource can also be uniquely identified by an alternative, more convenient property that provides a better developer experience. -Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also a unique property that could be used to identify it. +Take a look at the `user` resource: while the `id` is the typical way to get the resource details, the `mail` address is also a unique property that can be used to identify it. -While it is still possible to use the `$filter` query parameter, such as `GET https://graph.microsoft.com/v1.0/users?$filter=mail eq 'bob@contoso.com'`, the returned result is wrapped in an array that needs to be unpacked. +The resource can be accessed using the `$filter` query parameter, such as + +```http +GET https://graph.microsoft.com/v1.0/users?$filter=mail eq 'bob@contoso.com' +``` +However, in this case, the returned result is wrapped in an array that needs to be unpacked. When the uniqueness of the property within the collection implies that only zero or one results can be returned from the call this array provides a suboptimal experience for callers. ## Solution -Resource addressing by using an alternative key can be achieved by using the same parentheses-style convention as the canonical key with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. +Typically resources in Graph are accessed using a simple forward-slash delimited URL pattern (this pattern is sometimes referred to as key-as-segment). + +```http +https://graph.microsoft.com/v1.0/users/0 - Retrieves the employee with ID = 0. +``` + +However, resources can also be accessed using parentheses to delimit the key, like this: -The following is a hypothetical sample: +```http +https://graph.microsoft.com/v1.0/users(0) - Also retrieves the employee with ID = 0. +``` -https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0. +Resource addressing by using an alternative key can be achieved by using this same parentheses-style convention with one difference: alternate keys MUST specify the key property name to unambiguously determine the alternate key, like this: +```http https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com`. +``` + +In the same way as requesting a resource via the canonical key, if a resource cannot be located that matches the alternate key, then a 404 must be returned. + +> **Note:** When requesting a resource via alternate keys, the simple slash-delimited URL style does not work. + +> **Note:** Do not use multi-part alternate keys. Feedback has been that customers find multi-part keys confusing. +> Either create a composite single-part surrogate key property or fall back to logical operations in a $filter clause. ## When to use this pattern -This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides a useful alternative to the client. +Use this pattern when your resource type has other keys than its canonical key which uniquely identify a single resource. ## Example @@ -82,6 +104,7 @@ Declare `mail` and `ssn` as alternate keys on an entity: "mobilePhone": "+1 425 555 0109", "officeLocation": "18/2111", "preferredLanguage": "en-US", + "ssn": "123-45-6789", "surname": "Vance", "userPrincipalName": "bob@contoso.com", "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" @@ -94,13 +117,12 @@ Declare `mail` and `ssn` as alternate keys on an entity: ```http GET https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 + GET https://graph.microsoft.com/v1.0/users(1a89ade6-9f59-4fea-a139-23f84e3aef66) GET https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') GET https://graph.microsoft.com/v1.0/users(mail='bob@contoso.com') ``` - - > **Note:** When requesting a resource through its primary key, you might prefer to use `key-as-segment` (as shown earlier). Also, `key-as-segment` does not work for alternate keys. - - All three yield the same response: + + All four yield the same response: ```json { @@ -123,4 +145,24 @@ Declare `mail` and `ssn` as alternate keys on an entity: GET https://graph.microsoft.com/v1.0/users(name='Bob') 400 Bad Request + { + "error" : { + "code" : "400", + "message": "'name' is not a valid alternate key for the resource type 'user'." + } + } + ``` + +4. Request a resource where the alternate key property does not exist on any resource in the colleciton: + + ```http + GET https://graph.microsoft.com/v1.0/users(email='unknown@contoso.com') + + 404 Not Found + { + "error" : { + "code" : "404", + "message": "No user with the the specified 'email' could be found." + } + } ``` From 3ce798d3126076bd3cb50b440b525b206c195d04 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 26 May 2022 17:39:47 -0400 Subject: [PATCH 384/729] updated images --- graph/patterns/LRO.gif | Bin 14513 -> 15174 bytes graph/patterns/RELO.gif | Bin 11528 -> 11411 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/graph/patterns/LRO.gif b/graph/patterns/LRO.gif index c95bab5f03dfb17725c7c10d56774e928d98c8bf..fef90ffce003259ba8dea96c87ca1bc7893d8af4 100644 GIT binary patch literal 15174 zcmV-MJGsP1Nk%w1Vg3S}0rvm^0000004x9iFaQ8F001}$2nYZGRR928003bC0A(-$ z05kvq5D*YJ000ye6gU7l7#J8?001}uWgHwFVE_PS001B$AOJ8hBqStd05||FaR4w~ z05~`(C@5tBWdJaF05E|7IAJU-EC4uV05pXFG>J4c05C8x062*NIFUFw05C9BG&D3g zI5=H2002ydG&F@cIAugUJY_flVKg*2IFT4oX@M{RWjHv6GysV-07ytk09~$$H~?ih zWdLQ7OiWCXH~;`;wq-bxFj;+2P*9OLI8{|uVPOC`Wo2DmFp)TASy@>$U8-SWG&p6E zU0q#~IFV&#I5=gtVPRp7VE~<30A*!mX=!PyT>xcek!@{lu3-Q$eZG-pIHg@MWo5Rq zVE}P)akgauk!58xjh!@w&UJNlwq-atiPCv_c{q`_g@rVIeSJ8Q+JS+As(~=Nc`%8I zVTFZ-k&!rwiHT*Eu4R$h#(^-6jg88IG{S*h%7rwMk&(`YG`5jAnVDf-q2|tsG?kT= z(up+Ei8!{AWy*ZM(Q!se02 zwzjsm?y6mZ%Iey-Wr@U3B+L8aZk&)W}%F4>F()PC6w&udR=EkM6 z()O;}+Ro0-uHN46&V};Mh0@Z}rRL_g+W+#-s`k=}*4EbA+P2=_oxgulc_O}1F|Jt^)_V(i9;^yYY&hGBE|JwiB+UDlw z&hqlM|NqkR^6Kj9?(WX&_OjCU_U`WP^778|^3vM>+Vb-9_V&`+|Nr*(_WJtP|Npl7 z`uhL>+W-ImEC2ui0R94-0RRa90RIUbNU)&6g9sBUT*$DY!-o(fN}NcsqQ#3CGiuz( zv7^V2AVZ2ANwTELjMUzoIisefnl>>QAfZ$-l1Pd7AV(37(uHCzM z^XlEpx363afZZ~fkS(j>!-x|rUVIeX1P2Zvyp=n*ujR{_GgA&c0YU*N{TPd!v$pMI zxtUX|UcK3ZTL%SP3EX0V$&o8q+Ps-lCy(zwz=I1PPQ1ABE|hYW~%9?c>4J!D1kP@Vv|pX#%M!x zIBQ z22_|@u0eA^&_*4DG=d2z#@0LU!VJS(t+lseqH7&j_!^-I77{y3yTA}b%rYP^^UTO3 zmu&LMD5tFQ${~YH3^2P~3koGH9`WtJIOnXhl#W92BP9qQ+i}YxuWa+jA1&(Vj$a31S%E^qRZ^w(gsyfn`D9f#(i z3(y2702rXm<$DJ%I59DgB5@uaTrsuIbxcslqzJ#eOfD{Lt~n8>qb_{knVUX*;Da}P z%F}~vp>~8PM1D%#%ha6>IM|z`{r22<@BR1ShcEv2*n^V|HP5i_%FKvBfdROU$1nf< zCS_y66}Y<+cjeb-KYsXD09_Zf_qh*T!yL$n0O$lz`3=l$ahC&(W#s&_r@t>cG4 z45ASKhe*UC8u5rkG>7>lNQ}Z^q7m+cAN{6C#VS_BepkSnEr`KCaF_#$NVG--24DgJ zfIu7Tz=IjlxQ-NR?f}pLV-dBt#AU>geHUb0?fL*T{fUf(*Z83r0T2i|8Zw9}gySKH zNJsRsZh4eA0|eHFLqxo7i_hrc58uJbPI~f_pbVubM@h<1{?HuR5F7=yMTPfS@s+TQ zhu}e{&(tPQ2A|gZ}J3UTd z5R@2&2M1ZmPG-TEz`Q^i0kDB#zGDx{45l64@y!8f&IY~H+yLw_NYEVs5W#E(IyHCy zfk+S&beh{hD$c0@(Aj_&>%)W^CYhEK80?mH`ywajLD7m@^r9Hes75!+QH*kO9ZHno z9VyXDDa!JsD4icI`?y1Dcu|+{=;%h_kploEq8Z9G!#w-|01}{sqNo5J0%xZ}#*SWVORQaQfAG z1a+ux4Q3fP&C`I8?u0dyQm&GU|P|8IQFrSjjUuR zOWDd=_OTQ_YAWxDiIS%Dv!GQREf3}jU}RFFF`dU{DeH^{Aix{<;6hK?fmL?Z++(al zh3#r1ThWh#BdHZ+sx=YO35ZU00I6s!PZ7X|V8+6+vc13pzUo-4RsjI7jVwKC`iOhH z0=uyNF6g=&+n#DQyW1Ur5&&?3QlK)L*;*(iS>USDDpr>i-K~E2%U{gS;~gMPWfNWJ z+0YvJz~Qpr7NUAu{HnIUjwOU%9sAt@;6lOx9Y7gG0bU3@HnZ#atsPyVE#WS%cEmNV z0)+YCdq}thS}lP)A`H+PBLE2iknwG4P-8QAx5MJ?@Q<0B;~CRK2LP}`JTE{5I}ErJ zZT~3j8R**y`({I;`E{&5w#?-&d-=;?4zrlY4B7v_~~(3^bzwDM(1*|opaKAJT@($-I=#Ie^$P$12E90kRt+$V z<>5Ll8}Im)KYov2*FxXc5_&9{V-T(gku=iQ` znq#}prw+On0Du6q_(Jc0KX-^1ToGSLCjeHEiBmIdu~NdUj#D8TyfBky>~ zhk5cLrpC(Iy!rauA0!I)e9a#m`Tnmyv+p%hN=G}g*L3)G3?l<{)#rZ+|W$<~FA_=p1Wg*11AaTteYD1)%KbTzmP zyyk|LIEHgrhk!SMLs)@&79AmT8!o$H;%RS%tucn=h!FNCNqh|4NyUsOgr#)|%nih_M-+ zu{4{^XPd(bnz;#RylIhZh>`I~o1%H0Sx1J(374V>eoOhCzQ~-C$9iX0d9Dea(m9`0 zG?ou0VW(D*H5Qmmn0&fGZyeBe#J6Qk$N`yva6Px5t1yM8#tjsAhG^N2L*(vPz-RlZ-17cXD6iMAZohxpyY;V5;`vF zb_zskp+5SGQ#zzYdZhoEq)X~{PO5K^5Tzv=q@!k~|Dl+1xu%&J*Y!%Uk-z{+-06<8SE`JqTGSd@;hL>^_O0DIUEqqXjzz9QcCIL? zsRY}u?+U8DDwl^^kyTslT2syy zuwR9;(<)RZdk=4gPYUa9DGLCL@K!{1RLuA^^*W)~gbC9_a}~Q&*cG#ng|gcvPtSD^ z-U?Q;Rb4RaSTmchQM8DM?8QulrnU*?wKtSQ z+2lbK3q;MdyS&@GACtJ43{4ye7OTSfoWQ)j!&!MtUXy3XDb#90+?*P@z)_E(|&wPzvjC4$>D; zJe)!>yhc5ozG);t*yBhsGc$uAzp-~U63az(v%%(&!vHnJXH>+5RKr({!!@?UJ^aJu z6vUeo!&+QOIUGXgGdPyl2owxG{}&=TJH$VBQ$BWl$9SB_dc4Pctj7_wIuyh{OyIF6 zT*&%iKU9D_Ln|`Y)5nf%#|6X;k37jP^v5IoG=1R2nkXr6Q(97dHBQbqoGkj zxxCD=TtK|Q%+RblE~7k53_Jb8I8}p9#icbMGdIzk%g+4G%)HAJBty*OJgO`L>2ffb z@-7|YF&{%T@;uMf3TnE5B%m97J6Tk!+vo0Ov&iQ=L3cUlG=JY6p|P1Ai6G5Ycbg>onXebmi10Y@+_2P4xz?JhkX)lxmx zKK;`{tu4CZA{KDXixV(vU^VIjD>GfwR9)6)jnhwkFSnvCVnPKL11uQ8BNl=o98xK7 z0xOr2*LuCzeBIZ6o!58$DS?tAOVT7eU?@nP*o@&QK%gcCqam*n*nd6QlwH|?ebxZ;S0{-PyhrwAOj4*1Y0UDZBPL7UElYe-}=qpB%b3sZr~=q;0?|M56%Jw03rX- z2a@p?a6uWBu^N400T5sTOwJcdjsbuX7Y=X&>rmlZZWQa#0+In3NlqD2z84q(0T3|e zQm*7xZUF^w28}}rM-Jvnj^=yO8B0#*cA@4~E*Z-p5c(jD@l4W!-ZFy#IfH)aAJZXQ zzUV};4)A>FgT4z?umzBg=*ut&!Avf({UCpVUg;oR2UKtho(|F<;|h&{6s~XvYs2WQ z&TR8=26K?uop1)=ursN?>aCvZZT9N1j?}YW>pFAmx<2d(#_O_9>$a}y#J=pHW$eC= z?7^<=%s%b1&cGm)V}RgWbFZs?a{97+&=D&^|>0WA4L@?l`0F^uF)qV(-F?@BgCj{66rf;_rqG@Z}=#1i$czV(^8G@P*{^4v6p$ z3}5kyQe24*4vlaR7T@t6|M4In@*+R-Bwz9-fAT1w@+!abEZ_1j|MD;&^D;m4G+*;J zPmbAe28D6+Jl_!gFb9Z`*ubC$6d&$AfAk9B@FxuMECh_CXBAE=CvBaZ+0C?ENNI{7zJ`Ic|; zn2)2HZzG)F`6Pc2?*8oXjx+B-2}Mu(Ge13vZ4Q*c@vdL{wtxG$pZmJM`wNi`FV6cY z?+-`8kMS5 z$CWK##+;QJIL@6te+C^|^k~whO`k@cTJ>tytzCoLhj-)6xq}k~YXIPhGM<3v_VjCZ zCPz(jtp(PId?!KSzea1t?h`rkjEvcWzL zH{`HG4?hIay*Jtzggg^ZOz4y|)?C zGr(b!DKuS)vrapYk}^59kgQO+C)=6=OQQa1OELc{a1yz}3d@fupYDV-QpmKMY@ReD z#gr^J;mkCmIXeY4(>(t{>o1WmN$H1g{&_1=m;BhNYb^_pBz2^LAbip%o`M<*u}4D% z_Pa?Xt4bhC)eJV-rZ!b}p--EIHoQ^!nE?O}atqWSLk1);8VPr`RZzs1L{f+=(ZI4g9b)$0fu7a3-^;2w<&;$&La?``)CpJ_e;4iws(A~lmtltU z?U!GPFFp~CK{4hyJWPr`DB_OorC48!M@Bhil~+zkWR{aH`QDRXrnzRD?REL)Ok=ip zW}SZqI%qa?7P?NJ;q|#_rI%(}ETWq}@#uGxhPrC2ub${>tTU6AI$El?20Lu9wHDj4 zuA%+9Y_-=8nrybmI=k7l-=@26l<&FJ?#AL)_MK7Q(K~R#QMQGc!O1db6z>jaym7}L zhdgr0C#Sq}%P+?~bImvBymQY#2R(GrM<=~>(@#e|b=6mAz4eh=ebArf?)R- zm@|N}z4yucd80(%RWxVexqV0eZMRkDJK5uthhFUEQ*Zx1*yp3i{_5#buO8Uzvj?B) z?N9GM)bGPb(VuJ#MxU|qO)nqR^V^Sz9}i@;|E|9Fr!Afe`g#KUx`Bfhv>-x+YCsDZ zq!R)fkbYlzU+Lo4srfySVD}S<{ba?6zx1So8nn514)eQ->gC8aI z4+<^Fl?akN}I|6mX(BjjKPedr5A{X-am%F88)c&kesk%>CgN)1PMLrmom ziU=9Z{`U04fdsLKYw?E&57Lbow8M*B90(a@bw#I0QFK&{loi=n5FYaIj9$#*O{Ty; zfsg}+cO0P~5i`f8xG{8agp?c!DMbAF5Ri;ah$@d_;4MNGaSR8PBqbNg6hndzk#|z$ zBJ1c#Ab!w?1@Q+O_BfCen(%(9T;(PUnYZ43(oUfyo-vkd2 zCju*1V$&hrr~&&ZO3{gQ1Bn{>C`B;J#~q%MDZq!c^b^brMpJqS6S- zOk+l~!cwPjRH72$DXB&{zye}2r7~UU)f)Ox6#X2i{rP6Q2uylO!hQK5y5 z)G}9v>Qb_55OR>g3nLWb3R^gku}-O~HpQt$!b~8nn4)0l8kD#Xi}H9 zREV1Br+#P zPR_|$!~`{{Hc1jchT#8&l{_}GAU!QhJ{wv(&Xz#N7|I<$xLDofL$apFJ@Uf`?eM7wKH=gB4dw^g z_ybp2+e+?IbtVs(L>S$`h(M9!t!%|=YctA^n-1nLG&b&yIh$k=4q1k*tN@osuvrO= z<9;lZF=Z=oWgh=C`5#UO1dodxRx|^p1#%vYn901Bu?Eo>yd=mY|1s7U=R+09#IlWX zT-8Kt8JQLdW{q#`W+xBB7XeIVgPQ!-tQH#5TUIimFpY*wSea0gUbHKB9Fi9w>c>I; z!=@2aSMlnkjCrxvAn##|6n7YKA?tOA#{=wOYj_leja@$s>Mu8)p$(&e&xZ|yruQKG zAJk^guqEn64vN1s`4e(R5cCgWnE!yUmr+ zu!7Ls9x$^R(dzGVyW!pO=C-MB8dgF7do2!kZARN#>qov;;U@w2Vbq;*jChq1ioy3` z4i2uq7`*=h@a_#y6Rz)~lpKU1$4HLJ?ZGeh@DnGG_kz2K);GhDAc7Rgu-4=mCf;o|*xNEY5*56qm4=QgQE_+8a? z&t$Dj{6{s&&;X>iwXKH9F9yR-Q5LR+i1kh;!Fw2R@d_S|a!+8v=lo9eO2(jSF-Un2ffS{kM^~ce$Y4- zZzd0NNr8X^>#i`|L8$S26B}gs#fJ79kmt-ep{Wy)X@fY|#9|)Tx!ZqHMa+%RV;$oe zgXsTWdq!Y&iScWs1z@Pg3@+hPGz^vAA;moGvET9Yr=KL@Cy3zf7jWxS7K5ICG4{JN zR*R5zLn`YVKuD9n9twt#0*WL+t6C#64GJnkSqat(qU@up2+6>Xd$d;Tz+qrNgR6mP zu!u<*!Jq;_lBhrRBa{+6LBZ3&^0S{+Q$Vysf(~lG3Zyv;Q6%#hVDAyQ4(6;2-k)gOn1jI$kQB+3bcl%59Fq(WlvK%>Y#EYt9hU?Tn3Tz! zq!*fO9h>|PoYYC3WXa6&$;<%Cpp?m>#GInU3}})jq=d?(&<8ch1LX0hg{h}!jLNNq zh|NHrdjJ`(6w9$B%d#}fvqa0ZRLiww%eHjOw}i{Ml*_rK%eoXxeV~SSvP+!=2i&op zdnly7e$gG?*-M?A%BmcmGgvm0IRiQ=%$!up%VEmPILgOVrpUCM$+V2hw9HMy%*xTs z%HYh-B&5%zoX~WP(IibaGR?_B&Bjp8)r6wfl$_UOjM$XT8KTX|vCYN6&D?yT-GrRp zB#Yk!&h`<`$05$KFwWx)pX79$0>u%OQBjGpUcoa_XP?c~nn@y^2m z&#!RG@f57Xv`WQx#7bh&O7#S&Y*yob&XG4)xI4;YrpJ(XJrT z62%-46`U01iWOy1%W=`bfzhpy(HW(j8r7|v8`X*&)ltdeQM>WctN_v=g`6R+n00FI}53m6OIKQ}i)YwL#OR z7}GVan>H<*H&u$klvD7@(57=wf5A%%%~Ry*%dZT~KsD4uMbt!9)HNZ8fYGEyMTo?y z9Y_rtk>CK27y%snMgB3SKm~_0P=isJR7$;>QUMM(IF-2Isx|%8s0h_j|0PwHA(yh? z3#9s%Lv2;5c-2^CnL;s$RE;p?Fw|U)j9tB$dFX>d7?WjX)@F6qXNA^imDXwX1fxI; z+OUUBjgXzlhH3TIZ*|r@$OCXC*K#e_>`0GLH3zNp7fJ|`cZJt@mDhQt*LtHnj|Exlc!sG6*^)KckJX*@=mTo7)1-(~=QPzYQi+XKRc?gR6a5@wm5P-Wh?Yf) zmp#tZiUDg zg$TR_+?x?xt0CNnFx|)+~sW?&t)3WWeCcZ-e#fRrT?*BXNlhI^-`s!p3G%T#{}Oi6<_Wl zUzz#d^VOT}RhsTq2<&y=P=Vi~k>7-%-}>#8`xTn}MTq_7Urh1epaI~65a0oZlmZ5t z1D>|_U0s9`n1WD;fgy+rF4OcSpY`n-2rf}wazai8fGmu|#v%w`cz`6hf(H1Y8MxF; z%^(+60T`ZB4Tc{MMj8+HP+i)&Kr4H#^_lRnOm}>XP>47khrHjw4ZVxqx&p~((u45d=E@G2)A|*xHgD)fR9r+h-}b~rtxa_0PBP3>!Azlu|^2BW)HR= z2*X|v#Qq1lZV$R1XZV0@g0SoNz-xoh>-T7Cfyiw4*zA9J>h`GWfw=1SIBDp$us0Zm zh!%)-I1M^}=hMh)fr*e2@NP6%@_ zjYhy>=>G*irF)odhMF(FnK0g$xTWR$9#3jX0avcIB0~sJP6#48BLyF1if}4~U~r3| zVVcozo8fPPu$cc2%j%@DN@9dJMsNdn2nClZ5+94LBJcwuHJhpMnz`_6``-VSHV*#_ zkNR+tXs*eULYycy_VT~Acsah%E*mEax+8M6qk|NPhwr-|`^l^l?4c*8Bn@(M9Dwpo z1b`o4i7EK-o_J%GX>po)@uZ>U@%%H5xC39%v@ql-ud;*$Av++~yFKE&G?%GrR`cXo z1LIgPZ{-RFT(M6uh3SVmKm_Cg1~q7@ z`{k$p>bL&u$Nucs{_W@f?)U!h2mkO76?5nV_7;CkX@rdK9ct(Ux`%bUKmSZI{)-v@ z_>YwNmzesu|2x6|i2;Z+;QkRTXz(DygbEijZ0K;It23@1Qmkn4BF2mwH*)Og@gvBP zB1e)eY4Rk)d1vOJZ0Yi4%T5Ae(qtHMV$Ga7ck=A%^C!@tKUK06YV>GFnFEoYyvg(_ z)TmOYQmuNDsMV}TmE~&f=(H=?uwuuOEo%|0*|a6Os&)9aE!??)^J>!}|J#FK?bAxl^a2op0|G+?!qG-P14cF9-k*DgcaVcOL+N0SXAxIC!KX(`35G>1%`>8efs$)pn(cH z=#yafNYIOB0)SybYO+Zvq>)NGDW!zI(Z@`H-8STq8mdUpj%DJmtBqmfpu3G1&JFkQ5mN*_m@TMg%z5Tjp zueSP{#jn2whX=5<0+&Uw!3}eV@URM##jwK_UkCB45|2f(#T`qBF{c`Ri^Z|WB^w9w zkRpdgvdJyyhH{Q7gJn)1>$rTgc8!4IF*SYMgEP=U3q3T^MH_uI(n%}5G}BEx{WR23 zOFcE!Ra<>E)>&)4HP>Col}#JOc|EpPgJ9w@F>SmP7|Ue4{kF_qwf#2TUB|7}+;v~3 zj(Xo5q>kTz2d=kacmHRVH{n$AVc;bK=ZWZFGNA-B=j2FZR=oEquyFXRb{?C0Fb|b-xV}Q;GjKWh0qw9RFURCSK zGX=ZjvfFMD?XfrR%b|$YAS6E-4D);A-G2{!>E1h^*YZ>~f4))BL;p|pv)?Xz?z_9V zJ@y8TaY_CK?azK)(l?d$0T3vW3(%*gC%@8J4nR}L+(Gz(g97!BAi}GlnCw?TwFpou z1l(Xx+$TNw?Jj=rQwTXG2&(LfiGM8o9|+;n!Ki#NhB^@;>f)!s6Cy+vEf_|l$j3V) zjIVM2AfgT1GJ`{?)G&!O!Qld<$iss80}b0di1GfRibG{_i%#TVrvB2zFS-PZ3f$oc z53(Zx5detAB7h1=7dkkSt{V>kA{oQ7#HWlgk8lATLI6n+e~*sW7)}8 zw!szpkflP3m4Yqmkcvo5$Pw}Q5bX6*b3!O4Os0sC#}QM5xI_&tZTU=v_+kqGASS5> z`AZ*d(;?VQ$Tvg6x552moDm$MInUXSa847Nm~y2w+X>DIbz=&QxWs=5aEAj<$Px_z z00Az+PuSrOKl=PfJ@2U@QY93h27M$!W#@~3TJxF|ogD#^pn~CLbfXp}fGMQMh>n78 zq9A2lL*+>tThu{8ZRmtK7V-{o(6pvDt*IpwvW}bbl%~*8h&g)7Q%)u%s6w?VQ4w;~ zo8B@Z{fMb~d_m7U_~H_bcwjJtiO?%rfOF(t7bBuP@@Y(Y*TB{d|qAfQ-nQxC$YD81oE1sOz9%2P~6 z{9tfe%@FXuDm&QmmYftRZb87xh(k$rxuBX*1@{--D;x@Z*UX-Dt1CqJ++#|kknEIT zi4b#42^a~<#*{#N5YbABv<5M4l+>#{5w#%?h-gG*3-XReAfmti{jYyFSyTWM*uRu$ zEkVFh-~wa9wgjQZf(g8wgghg{0NzGy;_}$oqX*^FqFk#{U%C{i>pu~{p`50k>x6vxj44- zb=Am1Il~Tv@H><;&#Vwh9_1$e*u|L7P-a0JM5+dvm8`}fgx>vmtY|=S5DIjkN$eHH z#+wxjU;JrFZ@SZVjccacN@)p(;vXp-2CH3-15L#B51)$lt5t1l7c=@@VN8rf2y*B| zFPhPgHb}7mggJ{QARPvEghU&lqKN|F335wNv9YQR!G-2B+smf5wO2&#lK!)H-7ppb zCV?n)tDD+Mklwigs&0z{;N6D2Yp==m>&gV%$cQGhnV(rr!CLhem|9?{Qle2ujOA%wgTRtQ~ zZZPFOS-E7I8I(X<9n;Wm)PN2!8mUIzkk3a_hyb3O5L;`{A!=6?_a5cpNPiEi zi~hr(02OEh-9TL5e=d5Oz(EbzGy4WG)Sd$MKsvk-|AS~8unf_6+z?dJfPnJup$O%^ z=5}{%Gyv!a9iqMM^Wl8<^CSK1FVNzI-}s#qAgtdc_){Cf8S!mZ{v}`a?OyNw-T~Rb z`868zIUw+PKmn#30t%C(!BGAP!z4_RbvcgtTobvSRO9R)^Z`ge{S*IbpaBvD3$`CX z@t*O?iu=Xi@~PnBY~Z|!;QQRucf8(CyaOfVS9YM4hOEK;(beM6fm#8Pb%_sKHGmVU zni0U28!^raRiXE2p|fz6S0T~kfFZ0^fD@_$hYS`%pw;5oK|ZkISOMNINf73=l~@H{ z<(1wwr9eBp+WTc88Ri2X=0jV>;T$5K>&}MA>@VL zT)9;xVxsq;$QnXoK5$+i_74ngo!;q>rV$=s@zsjZUWas59a3Q-k|HAB5iK?!hiKyW zKp~AyVdtrsUdf^^;u|Xxp?8?Wfw4~sMpp~;Rx4W2_o*04k&m27RdiirHMY<;?hg2* zST}-IMTigC`G+0i5jtiNa-~@bl@x~*OPW+&I<}+mpkoP^j~kf}_&iQHT30u|W1d-K zC&rnbm81FSqc+ZuIttSiVUY2}Pyg&Aq6Ame{iF0`(M7fcK@KD}Qcp=~my9(IM-HJg z$y{EPV2%aI8n77Y$XN!lRYOwGjh&E8{|Zkrl^B4am>JR-c1hoi8C*hu)j@6@?Z8-z zA=?mSPX7tXe?VeK5(H14SV`KBIVRUa%8o{Qq&RA0P%34AyGrAOYFU!tFo_}E_R(ECV`C#vL13Xz}9PLGu1 zP+C_w`q>E$q@8h|oBiZEVV5^5p4VaHLka>nqxb?NN+yh)o;7AAKf+m8 z1|<1(BlQSDHy)*9K9@pv;Vc3VGxpu?kf0C+Q*iQ`>|iG2tXXS%;YQ+Ea~jfeHRf&} zC!jf}N!AWZDyCqRAg;}TM}b;8|C*p%1p%k+nsupKrr{b6z*-uip?X@C5Lgv1Pt>|?y8Te4|e1XdrtgLrQ6ITU zhz$||w27OvjYD#JDV%*F{1xR;GTS;yn~YuG07hvZa$j(srv89VH-ur?J)3jNDSLnw ziU8nN{^nJV+jzncn$l^9onWA7E+6Go0EQr{qAFjBk{uoJ-54a$m?-F=md*^&kw<2e zyU~xIl4-M@DnDoe{jpy@jVYrd6uTv;w%sYc+1skNDu#5b3*sr0mImp8M6goCu#SY{ z^`iDbkfe2pEZE~RjU7q^Yq2uKup;Z?`Hi(sMYeVX>-}oiC>|PwUbse3wxydxcqUzp zt4OG8Rj_MEm>p4itGv!Dz1AG7z1}On=BvK$E5G)uzy2%02CTpiEWsA+bp!+eI}S}V A1poj5 literal 14513 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 H0U!Sl0tdPW diff --git a/graph/patterns/RELO.gif b/graph/patterns/RELO.gif index d84f9950ce03d388ea9e06268105fdd504a44652..ae012c348fa8d1b783b783710fb517ae98dddf3d 100644 GIT binary patch delta 11304 zcmV+@EZ5VBT9a8HM@dFFIblfxmjU+x000001Ox;C05kvqI0y&`003Q)89)v&fdDvR zEG#SlIAs7dfstVzEnzeOJUl!&IAvit0A)A;KtMn^IFUp|L}fTQ09l~`Wo3ml0EsjJ zNJvPDH~^8gCx0|us$pR?G-0k`VK_KtkzHL~IAOA7WjHuxwqapmWo2cNWdLbuX=P=R zs$l?aZEdb$0Fh-lvS9#madE0)G`3{`k!58xg~~LA&UJNlIEk`2k&!fs(zazdIEm7E zd3iXIwtanlVTp-2k=lugIDvtIWs#AIiD89>g^`gsiGPWSWs%yEk!7-pIL3i6jg5`U zfi$v-VakOxk&%(kg*3L2IL?VQm6es!i8RuQIL?J(wvlC-nVHgwVakzEVXE?BveMd- zIGvrHWwy3quJ&QF_KC8IWwzR(p`qH5Wv;Ga+L={lw*RH2rG={Uk+zYtvSEp`(yFSe z%Bo$7vVZojuCC^xU6Hoh&aPq7u3@sWveL3)>Z)C~wzl%BVcNE3iPEx#%IdbZ+Pb>B z@~&a_u3^%$vW3p_+P0C2&hqxMVZOe;iPG|k()N+sw%WF~!otG;wq=pp+Q!Dlw%U>Z zwvpz>fv(Q-k=p;V($dPx%Cge-&d$!#(z3SN+CtLO(%RaQw%Y&h&Z_d#iT2Wo^3JZ( z()QNY*4o;(s_N?Y(ysQ>va0Ux+S=O2=H~y}k*@Oc+S>o#-rlbE_R8w&lW_tV8_x3b zw*UXq^788H>hkig()RY+|F-V#?(*`^lc54UlMMq0f0qFO2>$@F2@-^eAfkf^6DnND zu%W|;5Fri}6sw-aix@L%+{m$`$B!UGiX2I@q{)*gQ>t9avZc$HFfqp4G%=#hn>cei z95~6`83GI#IB)===!Bz4lPX=xw5ijlP?HvgFw_A82;O39-O9DA*RNp1iXHpamI4Jr zi5iW{f3~e#w^OS!2#_a>gB?4TJdsi*%a<=@0t+5YxUk{Fh!ZFNo27~rC{KzA83AD$ z*~^$SYu?Pcv*(bp5WKiil}$0|8P{;DMWr zf09ZpvDl(yE=)G*8B*FSjBH_j*?_9hC-_9f2ypmIw_VKPX8iE9%nF6$Deid;m9Vb*pg~7 z%>+Bmu*4Q??6JrutL(DG2HQ+A!T^IMD466|gr=ZotL?VjdS%uZ24Z3=s=@#(EVJmQ zt1hz6LMsfVqio_xA8y!MMhD@JTW&McVB@bh00%7azyud;@WBWN?C-G8N}Gzje}Y`Q zX}1(tZ1Ke!nN^0MvE;IEzyBsI^2j6?>@POcJo|1e)FJ|h19jZn9Xe=WB-0DCQrEu(P%sr1B82QK*F-&k#t4Xrt9mnwH~+dj@W(Iz{Pfpv|NZ#K|GMrd7n94wlK>vzibuc#8qg%R zV89wMVK%g}j%~2Z-~G-J0Rf1hdgE{h^+1q8cc@}}){r3mU}uiq8O;{d>O=X?CpE=E zP#q$Sp$uoJKXsHae8m_ae<%VNyaW31hd{(g0{<5X3SY6$8?K|_{644yKSWLz?r>rV z$^pM*Bmi;-2%!?)VZ##gj&GPyq4`Dtqr?g0f$oc6Jl4p@HoEbRaEzlI=P17};!uYH z#J~W9$j3esZiozA-}2I^#yP(69X0@fB9w7OF~mcP=vd<_wwDb&0Nuc5Dx3MA%uR4onxAA{HM2=jeQr}6qU+{2_c+dm zIy6k>6eKRKdChm=V4jvE12Ki!Mt9=Vpf<#tGjkEIf9@)vNNnCH3(C?`?h~P`WSKX? z`Ouo$)I|L1|Is{YE~p7sOTuCS0Js2NInWJv z!c(nO)n-VqO0BGN^{|M=j91aQ(o?b{0v~0gMJ?ykb`}++{Cw+DQ94jV@)7`xh{t3} zU{81w6tHr9e<@*S%Fx8R_O&iq>`ntqTft_wq;X|xO4B*V$?_nr?`*?6XK~ugrq-%4 zEi6r8%iQLwM7DLUt#spo)RE$LjB=$bZ{_${(7w^HvjA^A27%Xi8X_L@v>XRaflMr} z)s3o6E^D1j-};gSy4#&DZL6!)+;TR$aV#rw-3VUre{M7w(a_!^{ zO>c45+R@`q@qo*H&vW1T*P2#$j^T~jH%GeO2#@8xaZPRY5|_O5geNgCU;@ZJdyNp! zf9oZZJzQG@pyIc-hOhg}Wnr(I;HPvltP{?1J}z9`4Bllgk!%12x1i&YBf&39I}1_= zBNU!CaJ6MV=zgQSAdAI!O2D~>$g&A&XTQ1B(S20laifEvc7Uv&z@tOTS zat|XNloK8J(n*<08?F-0P>*}@a2&;cf4ec-lO3*@^&Rbo`%;qkgapnRebFb}Q;TKK za~b!X_O-Ls?S#!^tmRJmiqTS{PObaj+lc@7i|SqG5=U!#H$O=O2tM?L2Y}(T$m1}^ zRa_oJ}9Ct*TD<@z1-fQdfR-U~RH_t{$iZ+puQ-qikAb?pIKOVref4~)$ z4}AcH;1iK^zV+9f$km5%e=yw}G_hOdZ_j><=-(pvbH525^4|CGhZ>a|ctqF1kb10_ zA0Mor!F41O1^3ho%b)_0^9uV107xJVo=||mqyfF)3C#BjA0z+<$batOe*l06%ts0P zlS$&|dSjG6-qt>`lYS;BL9xR>e_r!H?I%F-_kxzEK*-}jp)@^47CA?NK^Is908j{k za7CBX3e+=C8qf+Cs0Ef&gzi8Jkt2jU4!90HSO`N109|lH357!cAb2kvLxarYKHl?% zU>Jtr!#gbaJ3R!0X!t3|GfhXtJYk4C8+1Kyh&#H2P~t;A=FR@;hkzJ} zZUZ{~BQ1U72!E4?il~MPa0YT^OKQ`Ff;cyJ001AL4U^b7coROO13+REGx%~+RAVzZ zvo)r8ibB&hqk}Yvkcf-;ie;iPB6v2CXf>z^G;{y}8$b=W7&NMwLtx|xpZEnG@EZ)& zF&_gmC$o&q*o@BjjL;a3f6mA-&|-)XBQda;jbidGd)O<6A}rDvj^a3u;Yf`uvqOUL zGPi;Oxw0$cVkpI6F3vKK^jMGfc#rs)kMm;xpRxsu`` zD(%QXM`8;fnJOb$lQmhAOj0TFA}e)JE4M-c=|L(CWF$v|Bv*2jNSTyMxs*)Vlu3ys zS)w8BmmhH820NgVESZ&`u_92Qk-B0WL-&myj8mlF64k z(jl_~2WLJKny8tYs=1o~sEH7QpdTRe1zGR{Sc#an zNfx0Y1b%S_pt&9f!J5DsoWcp52Qd)V!5?Li1yG;>ZcqgZfA9jMu>(LL1VvB;OK_dn z`2^azo!r@--ua#28J^;~o!Geq)hPr*00cJ>12;ecm2sQ!NfxO<03+}M>^Ys(S)O_^ zp8VOL{`sByd7eP91MIm11fY=&FcnBa0Swxp4*H-F8le$N6%!z#7J8u&iWCq~p$aep z<`ADC3KqgJe*tQ-pcs0h7)lf?aHBkmB}g(H zAsVD!F%3}pCOvAFqfi7=aHKbimVNM{m>~#XfDJ-grAXtX4qxB~P9vpMYNcKZOIWI< zO~a*MTBdvyrdv9tX1b<6gr;MvrfeFg#p9+-L#A?if2V{)rx;_Wc-p5M7znuD4pQ zopP?|TC3{HDeT&=v-+-P60h1v`{Ow ze@`p5?*z48>$deEwPCxr0N?>G(6xR`wQFj%e~|~pptYC5wRmf^0RM0R>K zxSPAWySu#GyT1FoOe|V(?#E`jp%e;P@2Et3Xm9PLMU<}k7vZs5x zn)|taI|IP`z2Fo0jAyRz#Bv>P~mfWGegzVI8r@;kruTfg>uzxbQK`n$jU zOTVlOIRgL%uZy}80J+?71?A-db9=oPk-9|tzwaaoQd_!4yR)Y|xq5365DYmQf6%`k z{J|g`!XiAvBwW94um+RhH0S#p6Vbvh{K7CC!!kU>G+e_re8V_yoWnZ2!#v!>10lSU zySABYypgNHY74pATMt{%wLn}C8%(v?%fQDMBjKR*!xY|3pPN2cue_IceOSnXQx~aRmqf5mDT&9LAyJwIJ@zAjFDWb4K z+Qdo>0Hr&&lAyLZa1HfPxk${wo67=4+{6`p5f!Yxd>gV-3%GhrxMteN=mE&kO2%(` ztchyK^56n_g;#G(4}oB_9&ilJ>$f`#$AU}57cs|4oU}{Zw4YqHvHQpIe}EB!j3R^_ zr_R72jw})m?7S-x4h%vJCOgW-oDs)-BFTKJV<5P|>IkH~5!q}a+pM^LI?g3h&gP7# z=u9H&yv}mk&gAjV@XV(2JRbC1&t`hh;*rn#{H6RH9{&8#RvOUYfd~al(5Gt9-+>K| zFb@i?s+*7t5na(1ebE@5f6*Gf(Hz~;9{tfE9n$~F3Z)|Lr|tmJO*6(MUD9$I4`t8? zWz5od%Fy01&@%msH2oPit*X*I3e9{G;qcS4n$w=K)9@(|8BoMXO%ADhgO+mu+aLj! zqXf`=3k@&?P_O`{i@GjAaUM{_ruzV3ovK9L8Ag4ZOKlNp9kpA{f7WW9)J_})ZSBN8 zaLd&o)=Iq9hv2rb%m#41s%6a?XKkBnz1Dj>*N3pxYkdo#9JDYn#1-HRcdgWsT@Sm^ z2!>78rd+29y{U!$*N2S}iT&1*OAm705!LY6jExanaLcF*+L|qZ()~`L*7=g5poe^8G)uqtb z>rJZ2tr^OFnBGm_^!?v*j8R>?%1Zmypo>vyPz?=G-kciXf0;4hts2xHK@VJD-Mn<<9{ItBTg2p>;YN*#v-w~6u#lj5@DUsP(p5-WU3sAnxVXhKa z-VtT461R|If4ETPTLI>%eco2y)h6KJ7~$sE?cwM><#+xSc23&=J=xlZ6@fBxya&eYH?>v(Qsf==k40q9Xn zwTBSETK(FUAOkRP47^>s%{~MsP`XGR?bBZExUSpIUb<-x0Ac@-L6=(95z= zU+|KfVnZ+NOK%Z6&+8X96JVSNxe$&kjS=%Oe~x8;5%J(J5qk?69M+NB^S+R}W&jVR z@W53r;ZVEObZ_@~PvMyj=sjNv6TZ0?oZpY|_w_CKk=^%qPwM|j%?5riwpE_x@*o3L zt@nrE+q!=18Vu}iPY%*8*$YtjsU5P3AF`L9`4b-bli$D+Q1_MV+oQhJdT;nbTleN2 zfAYpFE!)@K+UFX$(%djr|dmz@Q-}H*V z{i)ygdhh*2yZMwqw-f%_Z}9u&ef^Q0wqgzb=O5l{&x{e95$-UK^>57LSPbf*!T|B* z%bH074IV`JMO{6H4eMb_81YM)cn%v1e-a3+;VmB)>v3$jMUuP> zy2dubGh2QC1|D4aaNl_h!pS2@`EusVL2bo`sQGl|&zUw;KCQIH!)#-soO%er`0;I( zf?r9m2Y?dkJ~QmD6M$ETdlrm8e-O23e5>EzJM>S#sc!0uo-s@Sz=PK0`T-sQ8)Qhm z@tm-$HT&>0i9qu*e6OyUpu&s33gOFX3o{N(&?NrSAdLSg!$e%sL#5D?2STK(fx*Go z77U9rkxsJlv6wCdAiu(h%umC5kW2|h1-T+`yb}?85=b3|EJ?{7>k(%N!(Fc`kcixTZ`2BVm58R z1@||Mu$jk}IqM8JTdU-f;4HP~^0S9b<&mM2dbp9n|Ce}KmCVzoDrvwM^)k&19s`Y( zwlyeY2}&U5kpsY4m!2(hs!>MllVZ7LO4)~X43 z;J9d%z=WSP?r33wiHg|QnB~>^)(SfYFaxuS=8-LiO6iDb&0^jozmms5m|!0araD;( zeiH5KgE78&UdVf+LT?B}Wd~n1fuQmOMRbgDCV34&9_f->^_y;UJ9Y~6l$!J;CoOqOe^wHwEG1wXuNuM`htTpj9N`r% zMU<^CT11zBOIj<1smDp0a+SkGrbli;Azbn+LN4-%Eq&8WoVag;$BN~q#^B6kcGDrL z+$17G=}mGflWR`E)aT+L4+)f~9*?-s07lZz`GiWHa6#u;R#%ODy6_F{i4;j>2ZP!P zR0agCe^v$tTDvTcCkMLgCs(*x&Sbjd3w8fEr8hP)4~llwCvb_wEqLbygQbTkK77Pj z2DGw6kd!9X2q~*zDpI&Czzk#ws040Wim|YBr^xatJa`HZv5*5_zL@Eztl3dyrb0VR zg=#5f$rZpn6N{Uf6o*t)RpHPmpkLK$Q350uf3h5nJG+c(TGhJNLRNzXNiz_Y^s}V< z6iGk!bW$)q;#I8%C{!qk*MyitFD$avu!lu#9H$zs@E~iYVU1RB_$pSgDlb`)BCBr} zn^?|v*0T`&=qxj~v4~BrY7px|Bj^#el#G@#TS1F@RLfewt_-jNz1;)9GSScW*0;Z1 ze^Y2}@;?Vr4jlHQ-|KKkgbWckG^$QH$@Da8>Bj+)ucJ_ZJv-#lkJ*V)c@f5vm3 z^}J_3_u0>X26UhWJ!nD~+R%p%v^$D{XhtJ;8VfO{fEzvO11I>DU8Z!VGa2SWVp`Lm zh9sv6>1j}xdXb_YB&kikYC{&Yl&WTRtn-L!LBd+rx27Yl{{v}jT=yD{x&|b#eLd_b z0{f4`CU&w%Xlys=wu2bU1h(H+b@N%-VeDeiB#TRh#aA({g8|Qd3ZvlYUqBi6}e;N6Ew{7x+ zpuCmhKtw8ZzH^@U+~+?BdeDVFbfOpC=toC-(v`k+rZ>IkoM5^6*wc>nL@84ihle_x zzICp5-RoZmd)S!{2iq__-lrt{Zq&YZwzu8wZ-;x_oRdCK#g7p@D-)F}@0v1q;Wp9g*DMGyA0 zN5m1$a5X8xU22GjP4j;DRm{;%^OMV)+>~`(J*Y#*0t5{}34M2d+OKgZ-dKh_Ix_%~BO%IaHx)&KU*^yS!n?-N`6e{m##`F(xSeIzSQ>H+5SuIa9%KE5Ilcz!%K3 z7*wJeq`^D0!6d@Le;ll{9Wgu(6mq%EAXn*c0&Iyn5;1~mkrz-WLWI32lg0fMWNvamzF zO13P7ATyLQG<*+P!V;69hbRz{oL8DJ4UWZM!4~aQb>V8`IVLU z#=?rm32Fx8e|REm{0u$8#eT5}19_ofTpE`E9;W{p#~~_5fp7ydF|})4$1hsBC>a2{ zaDnx?!+~TUTnU(Zw3~bE7Jc+TG5kW*pa(9{Lxp@3hRjD0gei*z7mRdBjs(euD2CeT zmgG2-oRz=#Y+<6ohuW4w+Prk7Nk!f3Qiu@rW>yNkrgDi}1;obOaZ4 zh*Yr2=yQk>Tq{smj!mctB%1`A1cf%Uh?TU-QLsvfz)G1+Nv4Pno1{sP2uqp7$$HR9 znG8yYNXwH1%B>j6ldOZWB&D=-gMw%~NV5otXgdH~qP8o8t!xOAT)Q%$jkcqkCMtur zTR_EZe>+5A3T|mTOo+_Hj4GO2G>=FHw=+tH|KLluJ3)u|%(c@?hoC*%2u!9pO^Gl} zrXWnUv&_M4yTx-%w(AX3N|VL>0z@bVZ1BzB1Ww<~K8J{h;55$P9L|PthvPg>bJz-X zU{2$7&d7|;;4DptFo)^{&g*Q5?9@)*^h=Kre~0h%&8YN<=~S!n{LQVH2lB**&?HXx zgwKX3PV!7nhFDJWL{E#z&+pvNZ*ouWl)eDn3h%7WVgSWW!iZu31>MAi{|w1Vdqlgz z8+T|>jl0i1d!<-HD`|olXbKKpswQD{lU~}DiK{O|o6x#pA`8_`rQEaeqNa8P(ORm} ze_Qf_-kDJ&!loS6rdRy46^$D|a#4+gP(1(pD1J&O`B^7=`U!a2pIWS^mANM+)u*%Q zCmi`F_vkByI;ev3QiP(cg<=T(0f2|HQuqK;KNHe)v7{o+13ydBm};q#+L@<#p_H;y z%i;=`0>_y06+l&;oAN825~`ju)S(iqe?%RNqB^RiQd352({MQ^cZh|x%(JN)jajNu ztfC8+;ws5{m&(E}%NnbmGAmO=v`OtFCNwm+f~&kblDaaItXhh=N)b`Lt}G$dUQLOr z8yttpmsL$r33F9L+bj}ktY#^ePlYT?(N$CRRm#$;%-V`%4K!B0n`13B;X<3RfAN9R z@|vuP7};7aBXO;0HCNi2)=~wm+(N6RTM{&F)s{%KFmJV;@cLN1e_9Oa^}=F-;| z+MVbMi~9&z>yp;&@`~-kB6-c$V$CpaCA9HkrSdv2i%2hGjZIae*gdzIS$qS`yL+N<5KtbLQMvA2cWB$V3|quaVIv$kCmwbfhTd*WN-@Y}!jt-u|Qf59c(&obOf z%G<=9vc(M!m1SAS{j!%erOBn-FLQ^Z&D_rQ+|LEw&=uX$CEe0B-P3(RZUTe+t9ii_qTfb+GPzzV8KJ0~23Rg5L54v%7UM@>O4~W#1Ze z-}mj>_=P0NrC$}Bhckd9Qwq=gB{7=?-~lG!0!}7<<2R0Tgj5jV(;c{itGYELk2Uo* zj5q@Zp54}ikNJ=gksH35Gl&ar;L!CrxZ1qG+r?_5JrOS5m20^Uf9AXthPKsY;a19p zHL%GX*5MuI;U4zk9|qze7UCh!;Yf%&h#QX*0KN&Xsj=u1B8K89mf|U<;wp~fGq8;d z#ssm z_T>Tt=3s_jR-Rj0Cg$)u-%%(7%uVL-nuoi5=4qzpYPRNUf5zr)*5+;I=5GBiUtp+% z|Mh0y5`>p*!Epv$O;$2aM(1Q2=62?+cZO%glIM9gta`TRwZi9oPOE+9XQ=Y$f9|M& z7U*&!=#=Y16N1QwSm;kQX1qmabZm_@lwdY=!}q#{IGjV<$wNBq=xsabeN4wd%*I
    M~-icQSvPUNm?JKtP5192WUnpV}AK*b(f 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 R{m>Wv(I@@VUyXnO06R>-sk8t9 From 8313a245cf63ca7ab13b90adee9c7bd9751e0ac5 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Thu, 26 May 2022 15:27:30 -0700 Subject: [PATCH 385/729] Fixed SDK link at bottom of page --- graph/patterns/dictionary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index a84fef89..f7fb0f74 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -209,4 +209,4 @@ The following example defines a complex type **roleSettings** as well as a dicti ## See also -- [SDK implementation guidance](./client-guidance.md) +- [SDK implementation guidance](./dictionary-client-guidance.md) From bbec4b31d53c1fd3009d287af70dd316db877065 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Thu, 26 May 2022 15:34:05 -0700 Subject: [PATCH 386/729] Fixed link --- graph/patterns/dictionary-client-guidance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/dictionary-client-guidance.md b/graph/patterns/dictionary-client-guidance.md index 966f3eab..14a0a219 100644 --- a/graph/patterns/dictionary-client-guidance.md +++ b/graph/patterns/dictionary-client-guidance.md @@ -4,7 +4,7 @@ *The client guidance is a collection of additional information provided to SDK implementers and client applications. This information is meant to help understand how various guidelines and concepts translate in their world and clarify a few unknowns. Always read the corresponding guideline first to get a contextual understanding.* -[Read the guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md). +For more information, see the [Dictionary](./dictionary.md) pattern. ## OpenAPI example From ef1a953ab354fdf160b377fa73bd6bccc658e772 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 26 May 2022 22:41:43 -0400 Subject: [PATCH 387/729] added RELO cancel --- graph/patterns/LRO.gif | Bin 15174 -> 15517 bytes graph/patterns/RELO.gif | Bin 11411 -> 11873 bytes graph/patterns/longRunningOperations.md | 48 +++++++++++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/graph/patterns/LRO.gif b/graph/patterns/LRO.gif index fef90ffce003259ba8dea96c87ca1bc7893d8af4..604ad47d3eb0bf804fb72ee95d8485d6b08332d2 100644 GIT binary patch delta 15429 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


    ShcEv4D004my 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 delta 11265 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

    w#UBW3lV8fv;Lc9jCp47Q!<`WES^rpQ7D21 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 diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index 3c76a706..cc4014e9 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -98,6 +98,7 @@ heuristics: prior to being purged from the system. - Services that provides a new operation resource MUST support GET semantics on the operation. +- Services that returns a new operation MUST always return an LRO (even if the LRO is created in the completed state) that way API consumers don't have to deal with two different shapes of response. @@ -118,7 +119,7 @@ POST https://graph.microsoft.com/v1.0/databases/ The API responds synchronously that the database has been created and indicates that the provisioning operation is not fully completed by including the -Operation-Location header and status property in the response payload. +Content-Location header and status property in the response payload. ``` HTTP/1.1 201 Created @@ -126,11 +127,54 @@ Content-Location: https://graph.microsoft.com/v1.0/databases/db1 { "id": "db1", -"Status": "Provisioning", +"status": "provisioning", [ … other fields for "database" …] } ``` +The client waits for a period of time then invokes another request to try to get the database status. +``` +GET https://graph.microsoft.com/v1.0/databases/db1 + +HTTP/1.1 200 Ok +{ +"id": "db1", +"status": "succeeded", +[ … other fields for "database" …] +} +``` + + +### Cancel RELO operation + +A client wants to cancel provisioning of a new database + +``` +DELETE https://graph.microsoft.com/v1.0/databases/db1 + +``` + +The API responds synchronously that the database is being deleted and indicates +that the operation is accepted and is not fully completed by including the +Content-Location header and status property in the response payload. + +``` +HTTP/1.1 202 Accepted +Content-Location: https://graph.microsoft.com/v1.0/databases/db1 + +{ +"id": "db1", +"status": "deleting", +[ … other fields for "database" …] +} +``` +The client waits for a period of time then invokes another request to try to get the deletion status. + +``` +GET https://graph.microsoft.com/v1.0/databases/db1 + +HTTP/1.1 404 Not Found +``` ### Create a new resource using the Stepwise Operation ``` From 308f53fe489843bd1e412a18f27131e74aeb4f0a Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 26 May 2022 22:59:15 -0400 Subject: [PATCH 388/729] fixed top-level --- graph/patterns/longRunningOperations.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index cc4014e9..f602a8b4 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -110,7 +110,7 @@ heuristics: A client wants to provision a new database ``` -POST https://graph.microsoft.com/v1.0/databases/ +POST https://graph.microsoft.com/v1.0/storage/databases/ { "id": "db1", @@ -123,7 +123,7 @@ Content-Location header and status property in the response payload. ``` HTTP/1.1 201 Created -Content-Location: https://graph.microsoft.com/v1.0/databases/db1 +Content-Location: https://graph.microsoft.com/v1.0/storage/databases/db1 { "id": "db1", @@ -134,7 +134,7 @@ Content-Location: https://graph.microsoft.com/v1.0/databases/db1 The client waits for a period of time then invokes another request to try to get the database status. ``` -GET https://graph.microsoft.com/v1.0/databases/db1 +GET https://graph.microsoft.com/v1.0/storage/databases/db1 HTTP/1.1 200 Ok { @@ -150,7 +150,7 @@ HTTP/1.1 200 Ok A client wants to cancel provisioning of a new database ``` -DELETE https://graph.microsoft.com/v1.0/databases/db1 +DELETE https://graph.microsoft.com/v1.0/storage/databases/db1 ``` @@ -160,7 +160,7 @@ Content-Location header and status property in the response payload. ``` HTTP/1.1 202 Accepted -Content-Location: https://graph.microsoft.com/v1.0/databases/db1 +Content-Location: https://graph.microsoft.com/v1.0/storage/databases/db1 { "id": "db1", @@ -171,14 +171,14 @@ Content-Location: https://graph.microsoft.com/v1.0/databases/db1 The client waits for a period of time then invokes another request to try to get the deletion status. ``` -GET https://graph.microsoft.com/v1.0/databases/db1 +GET https://graph.microsoft.com/v1.0/storage/db1 HTTP/1.1 404 Not Found ``` ### Create a new resource using the Stepwise Operation ``` -POST https://graph.microsoft.com/v1.0/databases/ +POST https://graph.microsoft.com/v1.0/storage/databases/ { "id": "db1", @@ -191,7 +191,7 @@ the Location header with an operation resource for further polling. ``` HTTP/1.1 202 Accepted -Location: https://graph.microsoft.com/v1.0/databases/operations/123 +Location: https://graph.microsoft.com/v1.0/storage/operations/123 ``` @@ -199,7 +199,7 @@ Location: https://graph.microsoft.com/v1.0/databases/operations/123 ``` -GET https://graph.microsoft.com/v1.0/operations/123 +GET https://graph.microsoft.com/v1.0/storage/operations/123 ``` The server responds that results are still not ready and optionally provides a @@ -218,7 +218,7 @@ The client waits the recommended 30 seconds and then invokes another request to the results of the operation. ``` -GET https://graph.microsoft.com/v1.0/operations/123 +GET https://graph.microsoft.com/v1.0/storage/operations/123 ``` @@ -232,6 +232,6 @@ HTTP/1.1 200 OK "createdDateTime": "2015-06-19T12-01-03.45Z", "lastActionDateTime": "2015-06-19T12-06-03.0024Z", "status": "succeeded", -"resourceLocation": "https://graph.microsoft.com/v1.0/databases/db1" +"resourceLocation": "https://graph.microsoft.com/v1.0/storage/databases/db1" } ``` From 6eff6ef28b32507c7e9c59c4cb7fbf0c2dcac0a0 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 26 May 2022 23:03:53 -0400 Subject: [PATCH 389/729] root node --- graph/patterns/LRO.gif | Bin 15517 -> 15568 bytes graph/patterns/RELO.gif | Bin 11873 -> 12111 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/graph/patterns/LRO.gif b/graph/patterns/LRO.gif index 604ad47d3eb0bf804fb72ee95d8485d6b08332d2..da47ce913faa2741c446c175ad8f0814cc825f6b 100644 GIT binary patch delta 14072 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 delta 14052 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 WelPfjulSBH`IfKwo^NXe1OPiGB*I(( diff --git a/graph/patterns/RELO.gif b/graph/patterns/RELO.gif index a9eb9d4372b7131ad5430f9eedb2e8b19ed2f13b..9a8406bebf7f3d8514e8d6c262a698a95632707f 100644 GIT binary patch delta 6494 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 EJF~CE7XSbN delta 6254 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 Y`Zes>vS-t-ZTmLv+`4!3#vBj;J5vc)x&QzG From fb2ca25a59182a4d05abdb87fc219e705c867ccd Mon Sep 17 00:00:00 2001 From: "microsoft-github-policy-service[bot]" <77245923+microsoft-github-policy-service[bot]@users.noreply.github.com> Date: Tue, 31 May 2022 13:10:00 -0700 Subject: [PATCH 390/729] Microsoft mandatory file (#357) Co-authored-by: microsoft-github-policy-service[bot] <77245923+microsoft-github-policy-service[bot]@users.noreply.github.com> --- SECURITY.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..869fdfe2 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + From 8f5851584775be3afce748646a0c758c2bb8c38b Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Tue, 31 May 2022 14:39:09 -0700 Subject: [PATCH 391/729] Update graph/patterns/subsets.md Co-authored-by: Gareth Jones --- graph/patterns/subsets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/subsets.md b/graph/patterns/subsets.md index bf535019..4c505df5 100644 --- a/graph/patterns/subsets.md +++ b/graph/patterns/subsets.md @@ -6,7 +6,7 @@ Microsoft Graph API Design Pattern ## Problem -A common pattern is to apply a policy or state to a collection of objects. With this, there also comes the question of how to model cases where we want to apply to 'all' or 'none' without having to special case these values within the collection set or introduce cross-property dependencies. Likewise, we'd like to model it in a way where it is easy to understand and interpret usage from just looking at the schema. +A common pattern is to apply a policy or state to a collection of resources. With this, there also comes the question of how to model cases where we want to apply to 'all' or 'none' without having to special case these values within the collection set or introduce cross-property dependencies. Likewise, we'd like to model it in a way where it is easy to understand and interpret usage from just looking at the schema. An example is where you have a policy that you need to be able to apply to users in an organization. You might want to support the default **None**, enablement for **All**, or enablement for **Select** users where you only grant it to a few users. From 47c680800a3ff221fe9b6ea34320a5fa15b2363b Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Tue, 31 May 2022 20:30:06 -0700 Subject: [PATCH 392/729] Update subtypes.md Clean up language a little to refer to resources rather than objects and drop some unnecessary references to OData. Also note that base types do not need to be abstract. --- graph/patterns/subtypes.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/graph/patterns/subtypes.md b/graph/patterns/subtypes.md index eff09726..8ed60495 100644 --- a/graph/patterns/subtypes.md +++ b/graph/patterns/subtypes.md @@ -2,29 +2,30 @@ Microsoft Graph API Design Pattern -*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This lets us model collections of objects that have slightly different metadata and behavior.* +*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This lets us model collections of resources that have slightly different metadata and behavior.* ## Problem -The API design requires that we model a set of entities based on a common concept +The API design requires that we model a set of resources based on a common concept that can be further grouped into *mutually exclusive variants* with specific properties and behaviors. The API design should be evolvable and allow the addition of new variants without breaking changes. ## Solution -API designers can use OData *type hierarchy*, where there is one abstract base -type with a few shared properties representing the common concept and one -subtype for each variant of the entity. In the hierarchy, the interdependencies of properties, that is, which properties are relevant for which variants, is fully captured in metadata, and client code can potentially leverage that to construct and/or validate requests. +API designers may use a *type hierarchy*, where there is one base +type (which may be abstract) with a few shared properties representing the common concept and one +subtype for each variant of the resource. In the hierarchy, the interdependencies of properties, that is, which properties are relevant for which variants, is fully captured in the type system. ## When to use this pattern -The type hierarchy pattern is well suited to a use case where each variant of a -common concept has unique properties and behaviors, no combination of variants -is anticipated, and API queries are managed programmatically with type casting. +Use this pattern where each variant of a common concept has its own unique properties and behaviors, +no combination of variants is anticipated, +and it is acceptable that callers who need to query resources by variant are adequately served by filtering or partitioning using type casting. -You can consider related patterns such as -[Facets](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat bag of properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). +Related patterns are +[Facets](facets.md) and +[Flat bag of properties](flatbag.md). ## Issues and considerations @@ -131,4 +132,4 @@ POST https://graph.microsoft.com/v1.0/directoryObjects "mailNickname": "library", "securityEnabled": true } -``` \ No newline at end of file +``` From 97ce157c95bdd0168e38515178d212e662a45c25 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 31 May 2022 23:40:34 -0400 Subject: [PATCH 393/729] updated POST --- graph/patterns/LRO.gif | Bin 15568 -> 18377 bytes graph/patterns/RELO.gif | Bin 12111 -> 15148 bytes graph/patterns/longRunningOperations.md | 21 +++++++++++++-------- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/graph/patterns/LRO.gif b/graph/patterns/LRO.gif index da47ce913faa2741c446c175ad8f0814cc825f6b..1c6c76be856a17a018b987fa308cb94d23d83a4e 100644 GIT binary patch delta 18256 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%0A9!d7N7#c$N`#10=^$PpaLpX z#{vck`(=y!Rp3aN2F-NYo1u(yw3`NIL<5$`18U!6qy;0Og=wtVPh8Cil0*rT#|biD zS6~hVMTHA401U>UNX+1PXu}(Ho)2b3bNEhg=wRUR;JpBRTpckHDsr&@yG{+SxA=+>u8g5g>#o?l)VADKC8YpL$2Ve%gjkP`NyNDgL_C&+?L0(geFRGkfH1h$bLgHN|BQYRB}2U9 zP?DoV=;Tb+WND1rj+s~uj6&1!L4QehizixS#9ZLGWZ=M4`KvHoTso7z#8ZShv@@LrbG?!!~hioBRE(~3T4xTB#BAoUR3|se7Q%0U08U| z7Xg{Xc6R6IXc%|GnLb#Jcz?OWc;*F#Er5jy*;16JcRpB}EyZ^_2muMGTCfc-=%*|2 z=TD~RN~llbl*s7(j%?^8f*KfP87NZ_Xip&6)U4-;jc1|hB#DBKcoGncs)T!bS5L&} zn7t^4PEClq7J3$1;FwQU7Ua1=+`m{S&S9s&oF-{}=V|^<8ZZKoA%BEp^^KCr1%*YY zT0H4dFllt2rk9olBbW|=@y6&Nj+AQ5nO;JJ5)GLm#hE%nSa|7L^v$60BZvA<)u`Z< zHVtI0DL^`iQ(P&Nt?BHnDVLVc;b@nZs>PTgYLTTWt^v-AfX-~`{{(e>43a9JlJ1L} zLDoVRjh1m)XOTz{1b+xWoN5y4#A3P)m|ae+)v0968i~xQRy2i}0uFDC>FJDWm+>m; zw5gLBnq!24t)hgGsVeV02$1Ry;p`=Q_$p=q+U^_$K)xyfh#IT1g>%G(vH~lVqDRrJ zpmLKVRl88;>PP@56qvqG9PK;MBseikIs=5vhXu2t`>6T_V znxJL|h-KSaAP$?=>S5d}S)2s0mg#&L>RBA!Hd?1Hq2`2tX4`k)q zm|72@y3J3jgu#a9z{+E_LKndH&a~BO!lvuTLMozNY;4F`JwE?WOD3TRMp?dw%)WZ6 zyF#U>b;SudLw_Y4KsuSi44Sd>M8LM3k)s4!41 ziMk_HI%qL?nt}2H*^-9U3MP}0tU;{L+1%|N;zg&S?SfKIopP<$+L~lc+S(W=wDo4pNLF9`MPL|BX4b7!*cW3g zY`0DZP6SY9=$3I58)&p<_3UnHqy@LNhE%`?YZ+&G1t$5n8;JCPud)d*`qmoDu-0Rg ztNuU+|9>=>bYv>eq^&O3tghpnWP1b%v;K~PFbI9vy|c1HEx0|Ee)bu(9WXO>}`^w)zERct-v} zWNqLC&e)7VmMR^}@kr3I<<*bo_|4Mj+uK5o3SLdsTxAy;vPdBE<0&%j6k3NmC*h9m zCx5?L~lKn3!_l-Ep>tjs2tq=Y+7O!Yt`@Xw($4 zEEg64k02@!>q*k9vo0zy7p)VLGDy&}-W{`@-ta&O?ZLKIC?lm8Li0yR^W0H0^F)^D zE=F3g50s)05~86uYXms6oj7;!LENuSXm7BCD&@|?{g97tUV@uEuwC9^<%c~W%9*XLuPH_=2?Hn0Pw=l zv_)F0HCqE{cRjU2;6fa1&lzw+TMYzS*%U;-l}!QTKbV76Ap~LTRPy=oyOimK^=RCV zD10i|zapArLs+|6wqs*hfD(arJ%1=?`vd`P#f);mI4~$5%*SYRg=ss5WfM$M!Bs-M zwoNH^U&qv6`_yg2cAQWGEeRY!a8);DF)BV&@Illsk-=2m|H?DD6G5cWz}T3iYV*w! zT5Wg~pq1tS=+ZnNES}nCZ+KPM{71dE0cS( z2}R)b9UOusaZ)8s6%cJv4IKnHfDtD6RUKJTG8#lQ+>yRqS(cgEw7M3yirHlQ4TKl; zPF^^MRVm<*Yh-wMWOOiyqeV4ic)avOCRx#5<3ox^k?G+>Cqa_wv0Q&2#D7OoEehAY zfI%ZnlOEJ}>p_~E0$R>ew|`B7D@2UxhHv+q`tmXhtXOpDr^$2f9W+LSM?d(2j|2Hs zGYzM8uBVNucXsXNvgNC#TIVh$)t1_+xy0FacHCm_=oBO(MwLRNJItWl8#P;??qYrOdG_Ql`Mt@RhdSDpA(5&x` zbb3ARvjHLp-T3SYxNNRw_ZXv3PIDJsYe94q&knYp1ncJLc@AL|5nl#`n%Ef zGw=F-{WGw4v#`S$u@}1(|He8*B($N&{I| zW~%NcTS~=sl|5kG=6nCiCUD}VaDD|}>LuC-ro3*ZVXEcFa{b*qW?Mq$T|^gUGM8n- z8RAd0U)Dko?T4s(AhuxUOgfQ5Bswr17i{d3hlU9tz(8%?I=V_XY_*Hn|s1wzV?@O42Wc3qdrXSayw$HXN9z<1U3}o9Jf4Ijupb zk+FtnYgc3^=*Q})h_&`VPMCdir(}D)Yr!aelBe>we1qtuiqe1i78dCBlmB>rKYo%3 zg<5Eboy7CICy7?};0h~1{Oi`Bm_H(yx&5S})49Iglp5lQFN_4B3?7)2Qya>LZ)dEJ{WsQ|8sX zmv3Lce*p&;4A>kce1j1uR=k*TW5D3XBGWL+%$L#YlEr^jZiPW|GHivpf#+@>GPm^S zXGf=*O#6~>&6G=ywl!VS9hp;A@ZfYm+CbQ9Hw#aDY0^&9ug-}ef*X?Z?%@( z<7_|n;N$N)l)V2iPC<9>1Fy3Pk2B7|1ub(hzWO5T(8CWw3{k`pNh~q4<`@A$MHR~! z0|fv=Xm5YOG;BL5G!0W+5da!fJ8nkzO5$Sy6%TMnHtfRVF{d5_tII49{^?=zh00+KzLW~B1P7|jE2yx%G;Z^r6UV>8R} z5Tc4YCQF*Hwm&zjGRB%{sN?V$^in$( zFemREltH{K|6L6xY_}~mBj?)t_SGJ1ivsEVA7tS&vPVdbM9B9vx_cK)F|Ag&r?QlmkW zN+&dNGuKF_d8TfH6P&5!fOHz%Nam7Q)slZ9L>Yns(>Fy+x=4ty);iP;sk1lT1&bzl zZl{FlI%Xh-E(l7dVIzp=zR}ff9I{Oc5Ntw|49OKB!d^@7udD8xShe9Y8&!sUX1NAl z$ljTcBH{nUyy~>6Tzi7oa6(;au3?s0WzX}D9rDlxA04jM-(1`-Zch$g_~D5!ey@M& zx-olzNdQ!l2`w2JV2TAS9FmESd>zubCWJh?2OJKS0reFQ=@sZc6?CoxO^{x?4dPJkqZQTS z&xuyVnc@Jj<%Jmr$9~3h;~U`^$CIf872hEd9qrf{Jm!jD#}iamSc0^F)IxtMe(9r$ z_Slz1GG>u{`ATHep_xY_rjdP-nMMeCF+j?3laU!i8#x(DQI3+7rIh0#^D+iJ{X>;` z>7e)if=W{=rj;M6<%nK63BBA>j=XeCmlickVGfg+#WdzIk(o?oE|ZzfbmlXm8BJ+U zlbY2%wB|LjnN4kOlbhZ2<~K3H8BTGIlbq!==Q+`tPIa!6o$YkzJK-5mdCrrb^|a?b T@tIG3?vtPW^yfcM77zeC(Av2c delta 15428 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 diff --git a/graph/patterns/RELO.gif b/graph/patterns/RELO.gif index 9a8406bebf7f3d8514e8d6c262a698a95632707f..196cfae09321f977dad63120610726a8c75e0ef4 100644 GIT binary patch delta 13291 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 delta 10231 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 xC75A~IVPE9nt3LgX{xy Date: Wed, 1 Jun 2022 10:34:25 -0400 Subject: [PATCH 394/729] updated LRO ref --- graph/patterns/LRO.gif | Bin 18377 -> 18309 bytes graph/patterns/longRunningOperations.md | 7 ++++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/graph/patterns/LRO.gif b/graph/patterns/LRO.gif index 1c6c76be856a17a018b987fa308cb94d23d83a4e..4f59e3569453dba76ffa5159589c3dd8b80a0172 100644 GIT binary patch delta 17566 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 F06QGfSoZ(` delta 17609 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 Date: Wed, 1 Jun 2022 22:23:22 -0400 Subject: [PATCH 395/729] change may to might --- graph/patterns/subtypes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/patterns/subtypes.md b/graph/patterns/subtypes.md index 8ed60495..9c0e1641 100644 --- a/graph/patterns/subtypes.md +++ b/graph/patterns/subtypes.md @@ -13,8 +13,8 @@ of new variants without breaking changes. ## Solution -API designers may use a *type hierarchy*, where there is one base -type (which may be abstract) with a few shared properties representing the common concept and one +API designers might use a *type hierarchy*, where there is one base +type (which might be abstract) with a few shared properties representing the common concept and one subtype for each variant of the resource. In the hierarchy, the interdependencies of properties, that is, which properties are relevant for which variants, is fully captured in the type system. ## When to use this pattern From 91b9f30ee399c82c6bdb9d9aafedf64be41e79bc Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Thu, 2 Jun 2022 18:48:53 -0400 Subject: [PATCH 396/729] added type naming conventions --- graph/patterns/subsets.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/graph/patterns/subsets.md b/graph/patterns/subsets.md index 4c505df5..338868e4 100644 --- a/graph/patterns/subsets.md +++ b/graph/patterns/subsets.md @@ -49,22 +49,28 @@ The abstract base class should also hold an enum for all possible variants. The ``` -Be aware that the name values and types in the preceding examples are just examples and can be replaced with your scenario equivalent values. For example, types don't really need to be memberships. The collection doesn't have to be a collection at all; it can be singular and doesn't have to be a string. +Be aware that the name values and types in the preceding examples are just examples and can be replaced with your scenario equivalent values. For example, type names don't really need to be `memberships`.The collection doesn't have to be a collection at all; it can be singular and doesn't have to be a string. + +These pattern type names should satisfy the following naming conventions: + +- The base type name should have the suffix "Base" and the enumeration type name should have the suffix "Kind". +- Derived child types should have names with enumeration values as the prefixes; for example, if the enumeration member value is `value1`, then the derived type name is `value1`. + ```xml - - + + - - + + - + - + ``` From fbc7af57efa610f0fb6d89e31a874962fcaadea1 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Thu, 2 Jun 2022 16:20:19 -0700 Subject: [PATCH 397/729] Final edit pass --- graph/patterns/subsets.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/graph/patterns/subsets.md b/graph/patterns/subsets.md index 338868e4..d08ea881 100644 --- a/graph/patterns/subsets.md +++ b/graph/patterns/subsets.md @@ -6,17 +6,17 @@ Microsoft Graph API Design Pattern ## Problem -A common pattern is to apply a policy or state to a collection of resources. With this, there also comes the question of how to model cases where we want to apply to 'all' or 'none' without having to special case these values within the collection set or introduce cross-property dependencies. Likewise, we'd like to model it in a way where it is easy to understand and interpret usage from just looking at the schema. +A common pattern is to apply a policy or state to a collection of resources. With this, there also comes the question of how to model cases where we want to apply to `all` or `none` without having to special case these values within the collection set or introduce cross-property dependencies. Likewise, we'd like to model it in a way where it is easy to understand and interpret usage from just looking at the schema. An example is where you have a policy that you need to be able to apply to users in an organization. You might want to support the default **None**, enablement for **All**, or enablement for **Select** users where you only grant it to a few users. -Existing patterns for this either have special-cased 'strings' or have tightly coupled dependencies between two independent properties. Neither is intuitive, both require reading documentation, and neither can be inferred from the schema or within client libraries. +Existing patterns for this either have special-cased `strings` or have tightly coupled dependencies between two independent properties. Neither is intuitive, both require reading documentation, and neither can be inferred from the schema or within client libraries. ## Solution -Have an abstract base class where all 'variants' of the subset are derived types from the base subset. For more information, see the [general subtyping guidance](https://github.com/microsoft/api-guidelines/blob/graph/graph/patterns/subtypes.md). +Have an abstract base class where all `variants` of the subset are derived types from the base subset. For more information, see the [general subtyping guidance](./subtypes.md). -The abstract base class should also hold an enum for all possible variants. The purpose of including this is to allow for easier ways to do query and filter operations on variants like 'all' and 'none' without relying on `isof` functions. +The abstract base class should also hold an enum for all possible variants. The purpose of including this is to allow for easier ways to do query and filter operations on variants like `all` and `none` without relying on `isof` functions. **Base type** @@ -49,13 +49,12 @@ The abstract base class should also hold an enum for all possible variants. The ``` -Be aware that the name values and types in the preceding examples are just examples and can be replaced with your scenario equivalent values. For example, type names don't really need to be `memberships`.The collection doesn't have to be a collection at all; it can be singular and doesn't have to be a string. +Be aware that the name values and types in the preceding examples are just examples and can be replaced with your scenario equivalent values. For example, type names don't really need to be `memberships`. The collection doesn't have to be a collection at all; it can be singular and doesn't have to be a string. -These pattern type names should satisfy the following naming conventions: - -- The base type name should have the suffix "Base" and the enumeration type name should have the suffix "Kind". -- Derived child types should have names with enumeration values as the prefixes; for example, if the enumeration member value is `value1`, then the derived type name is `value1`. +These pattern type names should satisfy the following naming conventions: +- The base type name should have the suffix `Base`, and the enumeration type name should have the suffix `Kind`. +- Derived child types should have names with enumeration values as the prefixes; for example, if the enumeration member value is `value1`, then the derived type name is `value1`. ```xml @@ -88,7 +87,7 @@ If you only ever need to support two states—All or None—without usin ## Issues and considerations -Given that we are using an overarching subtype model, subtyping model limitations apply here as well; for more details, see the [subtyping documentation](https://github.com/microsoft/api-guidelines/blob/graph/graph/Modelling%20with%20Subtypes%20Pattern.md). +Given that we are using an overarching subtype model, subtyping model limitations apply here as well; for more details, see the [subtyping documentation](./subtypes.md). ## Example From 813771814f608a721875b126dd8daf4a8fe3a167 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Thu, 2 Jun 2022 16:32:41 -0700 Subject: [PATCH 398/729] Fixed formatting --- graph/patterns/subsets.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/patterns/subsets.md b/graph/patterns/subsets.md index d08ea881..9931b49c 100644 --- a/graph/patterns/subsets.md +++ b/graph/patterns/subsets.md @@ -10,11 +10,11 @@ A common pattern is to apply a policy or state to a collection of resources. Wit An example is where you have a policy that you need to be able to apply to users in an organization. You might want to support the default **None**, enablement for **All**, or enablement for **Select** users where you only grant it to a few users. -Existing patterns for this either have special-cased `strings` or have tightly coupled dependencies between two independent properties. Neither is intuitive, both require reading documentation, and neither can be inferred from the schema or within client libraries. +Existing patterns for this either have special-cased strings or have tightly coupled dependencies between two independent properties. Neither is intuitive, both require reading documentation, and neither can be inferred from the schema or within client libraries. ## Solution -Have an abstract base class where all `variants` of the subset are derived types from the base subset. For more information, see the [general subtyping guidance](./subtypes.md). +Have an abstract base class where all variants of the subset are derived types from the base subset. For more information, see the [general subtyping guidance](./subtypes.md). The abstract base class should also hold an enum for all possible variants. The purpose of including this is to allow for easier ways to do query and filter operations on variants like `all` and `none` without relying on `isof` functions. From 79761526e93c008df294c738cb5e9fa498f66590 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 3 Jun 2022 12:03:35 -0700 Subject: [PATCH 399/729] Update Guidelines.md --- azure/Guidelines.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index b89cc68c..70cd6ac8 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -60,18 +60,20 @@ A Uniform Resource Locator (URL) is how developers access the resources of your :white_check_mark: **DO** use this URL pattern: ```text -https://.///// +https://.../// ``` Where: | Field | Description | - | - | + | tenant | Regionally-unique ID representing tenant. Used for isolation, billing, quota enforcement, lifetime of resources, etc. + | region | A code (see the table below) identifying the tenant's selected region. | service | Name of the service (ex: blobstore, servicebus, directory, or management) | cloud | Cloud domain name, e.g. `azure.net` (see Azure CLI's "az cloud list") - | tenant | Globally-unique ID of container representing tenant isolation, billing, enforced quotas, lifetime of containers (ex: subscription UUID) | service‑root | Service-specific path (ex: blobcontainer, myqueue) | resource‑collection | Name of the collection, unabbreviated, pluralized - | resource‑id | Value of the unique id property. This MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. + | resource‑id | Id of resource within the resource-collection. This MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. + :white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. From bcc49d20815ee9b1b0e4a01dbc23ee226eee34bc Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Tue, 14 Jun 2022 16:13:12 -0700 Subject: [PATCH 400/729] Fixed broken links, edited Deprecation topic --- graph/deprecation.md | 39 +++++++++++++++++------------------- graph/patterns/dictionary.md | 4 ++-- graph/patterns/facets.md | 2 +- graph/patterns/namespace.md | 2 +- graph/patterns/subtypes.md | 7 ++----- 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/graph/deprecation.md b/graph/deprecation.md index b15efce7..2a932ba0 100644 --- a/graph/deprecation.md +++ b/graph/deprecation.md @@ -1,20 +1,19 @@ -### Deprecation Guidelines +# Deprecation guidelines -If your API requires the introduction of breaking changes you must add Revisions annotations to the API definition with the following terms: +If your API requires the introduction of breaking changes, you must add Revisions annotations to the API definition with the following terms: - - - Date: Date when the element was marked as deprecated. - - Version: Used to organize the ChangeLog. Use the format "YYYY-MM/Category" where "YYYY-MM" is the month the deprecation is announced, and "Category" is the category under which the change is described. - - Kind: Deprecated - - - Description: Human readable description of the change: Used in changelog, documentation etc. - - RemovalDate: Earliest date when the element may be removed. +- **Date:** Date when the element was marked as deprecated. +- **Version:** Used to organize the ChangeLog. Use the format "YYYY-MM/Category", where "YYYY-MM" is the month the deprecation is announced, and "Category" is the category under which the change is described. +- **Kind:** Deprecated +- **Description:** Human readable description of the change. Used in ChangeLog, documentation, etc. +- **RemovalDate:** Earliest date when the element can be removed. -The annotation can be applied to a type, an entity set, a singleton,a property, a -navigation property, a function or an action. If a type is marked as deprecated, it +The annotation can be applied to a type, an entity set, a singleton, a property, a +navigation property, a function, or an action. If a type is marked as deprecated, it is not necessary to mark the members of that type as deprecated, nor is it necessary to annotate any usages of that type. -**Example of property annotation:** +## Example of property annotation ```xml @@ -32,15 +31,13 @@ to annotate any usages of that type. ``` -When the request URL contains a reference to a deprecated model element, the gateway will add a [Deprecation -header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the -date the element was marked as deprecated) and a Sunset header (with the date of 2 -years beyond the Deprecation date) to the response. +When the request URL contains a reference to a deprecated model element, the gateway adds a [Deprecation header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the date the element was marked as deprecated) and a Sunset header (with the date of two years beyond the deprecation date) to the response. -**Deprecation header example:** +## Deprecation header example -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Deprecation: Wed, 30 Mar 2022 11:59:59 GMT -Sunset: Thursday, 30 June 2024 23:59:59 GMT -Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_state ; rel="deprecation"; type="text/html"; title="state" -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` + Deprecation: Wed, 30 Mar 2022 11:59:59 GMT + Sunset: Thursday, 30 June 2024 23:59:59 GMT + Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_state ; rel="deprecation"; type="text/html"; title="state" + +``` diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index f7fb0f74..77a2ef2f 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -24,8 +24,8 @@ Before using a dictionary type in your API definition, make sure that your scena ### Alternatives -- [Open extensions](https://docs.microsoft.com/en-us/graph/extensibility-open-users) when you want to provide clients the ability to extend Microsoft Graph. -- [Complex types](https://docs.microsoft.com/en-us/odata/webapi/complextypewithnavigationproperty) when the set of data values are known. +- [Open extensions](https://docs.microsoft.com/graph/extensibility-open-users) when you want to provide clients the ability to extend Microsoft Graph. +- [Complex types](https://docs.microsoft.com/odata/webapi/complextypewithnavigationproperty) when the set of data values are known. ## Issues and considerations diff --git a/graph/patterns/facets.md b/graph/patterns/facets.md index da232342..90122c9d 100644 --- a/graph/patterns/facets.md +++ b/graph/patterns/facets.md @@ -19,7 +19,7 @@ In this solution, a child variant is identified by the presence of one or more f The facet pattern is useful when there is a number of variants and they are not mutually exclusive. It also makes it syntactically easier to query resources by using OData $filter expression because it doesn't require casting. -You can consider related patterns such as [Type hierarchy](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat bag of properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). +You can consider related patterns such as [Type hierarchy](./subtypes.md) and Flat bag of properties. ## Issues and considerations diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 841e5bc7..46d282cc 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -38,7 +38,7 @@ We recommend that a new namespace be aligned with a top-level API category. - Microsoft Graph consistency requirements discourage using the same type names for different concepts even within different namespaces. Microsoft Graph type names must be descriptive and unique within the API surface without requiring full qualification. -- A namespace must be consistent with an API category in the navigation path according to [Microsoft Graph REST API Guidelines](GuidelinesGraph.md#uniform-resource-locators-urls). +- A namespace must be consistent with an API category in the navigation path according to [Microsoft Graph REST API Guidelines](../GuidelinesGraph.md#uniform-resource-locators-urls). - When type name is ambiguous and requires a namespace qualifier, changing a namespace is a breaking change. diff --git a/graph/patterns/subtypes.md b/graph/patterns/subtypes.md index 9c0e1641..26cbda52 100644 --- a/graph/patterns/subtypes.md +++ b/graph/patterns/subtypes.md @@ -20,12 +20,9 @@ subtype for each variant of the resource. In the hierarchy, the interdependencie ## When to use this pattern Use this pattern where each variant of a common concept has its own unique properties and behaviors, -no combination of variants is anticipated, -and it is acceptable that callers who need to query resources by variant are adequately served by filtering or partitioning using type casting. +no combination of variants is anticipated, and it is acceptable that callers who need to query resources by variant are adequately served by filtering or partitioning using type casting. -Related patterns are -[Facets](facets.md) and -[Flat bag of properties](flatbag.md). +Related patterns are [Facets](./facets.md) and Flat bag of properties. ## Issues and considerations From 9b6dd127da871758704cea6442e3679b28f3d68b Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Tue, 14 Jun 2022 17:53:26 -0700 Subject: [PATCH 401/729] Graph Guidelines: Edit pass part 1 - Sentence-cased headings - Checked all links - Fixed broken links - Fixed some table formatting - Fixed branding - Cleaned up Patterns table at end - Fixed other formatting errors --- graph/GuidelinesGraph.md | 256 +++++++++++++++++---------------------- 1 file changed, 111 insertions(+), 145 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 2f9614f5..dd0b2b90 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -1,22 +1,22 @@ # Microsoft Graph REST API Guidelines -Table of Contents +Table of contents - [Microsoft Graph REST API Guidelines](#microsoft-graph-rest-api-guidelines) - [](#) - [History](#history) - [Introduction](#introduction) - [Legend](#legend) - - [Design Approach](#design-approach) + - [Design approach](#design-approach) - [Naming](#naming) - [Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) - - [Query Support](#query-support) - - [Resource Modeling Patterns](#resource-modeling-patterns) - - [Behavior Modeling](#behavior-modeling) - - [Error Handling](#error-handling) + - [Query support](#query-support) + - [Resource modeling patterns](#resource-modeling-patterns) + - [Behavior modeling](#behavior-modeling) + - [Error handling](#error-handling) - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) - - [Versioning and Deprecation](#versioning-and-deprecation) - - [Recommended API Patterns](#recommended-api-patterns) + - [Versioning and deprecation](#versioning-and-deprecation) + - [Recommended API patterns](#recommended-api-patterns) - [References](#references) ## @@ -25,8 +25,9 @@ Table of Contents | Date | Notes | |-------------|-----------------------------| -| 2021-Sep-28 | Using summary and patterns style. | -| 2020-Oct-04 | Initial version in Wiki. | +| 2022-Jun-14 | Edit pass for formatting, links | +| 2021-Sep-28 | Using summary and patterns style | +| 2020-Oct-04 | Initial version in Wiki | ## Introduction @@ -50,9 +51,8 @@ Together these guidelines and a library of API patterns serve as the means by which API teams discuss and come to consensus on API review requirements. Technology and software are constantly changing and evolving, and as such, this -is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface. Consequently, this document will more frequently change to add guidance in areas previously uncovered, or to clarify existing guidance. It will less frequently change the directional guidance it has already provided. [Open an -issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest -a change or propose a new idea. +is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface. Consequently, this document will more frequently change to add guidance in areas previously uncovered, or to clarify existing guidance. It will less frequently change the directional guidance it has already provided. +To suggest a change or propose a new idea, [open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose). ### Legend @@ -66,30 +66,31 @@ This document offers prescriptive guidance labeled as follows: :warning: **SHOULD NOT** adopt this pattern. -If not following these advices, you MUST disclose your reasons during the Graph API review. +If not following this advice, you MUST disclose your reasons during the Microsoft Graph API review. -## Design Approach +## Design approach -The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote API-first design approach where you begin your product design by focusing on how information will be exchanged and represented and creating an interface contract for your API which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface from your implementation and is essential for agility, predictability, and reuse of your APIs. Established interface contract allows developers to use your API while internal teams are still working on implementation; API specifications enable designing of user experience and test cases in parallel. Starting with user-facing contracts also promotes a good understanding of system interactions, your modeling domain, and understanding of how the service will evolve. Microsoft Graph supports resource and query-based API styles which follow HTTP, REST, and JSON standards, where API contract is described using ODATA conventions and schema definition (see Documentation · OData - the Best Way to REST). -[Documentation · OData - the Best Way to REST](https://www.odata.org/documentation/)). +The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote API-first design approach where you begin your product design by focusing on how information will be exchanged and represented and creating an interface contract for your API which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface from your implementation and is essential for agility, predictability, and reuse of your APIs. Established interface contract allows developers to use your API while internal teams are still working on implementation; API specifications enable designing of user experience and test cases in parallel. Starting with user-facing contracts also promotes a good understanding of system interactions, your modeling domain, and understanding of how the service will evolve. + +Microsoft Graph supports resource and query-based API styles that follow HTTP, REST, and JSON standards, where API contract is described using OData conventions and schema definition. For more information, see [Documentation · OData Version 4.01](https://www.odata.org/documentation/). In general API design includes the following steps: -- Outline the key current and future scenarios for API consumers +- Outline the key current and future scenarios for API consumers -- Define your domain model +- Define your domain model -- Derive and name your API resources +- Derive and name your API resources -- Describe relationships between resources +- Describe relationships between resources -- Determine required behavior +- Determine required behavior -- Determine user roles and application permissions +- Determine user roles and application permissions -- Specify errors +- Specify errors -When creating your API contract you will define resources based on the domain model supporting your service and identify interactions based on user scenarios. Good API design goes beyond modeling the current state of resources and it is important to plan ahead how API evolves. For this it is essential to understand and document your user scenarios as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. We recommend creating a simple resource diagram, like below, to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. +When creating your API contract you will define resources based on the domain model supporting your service and identify interactions based on user scenarios. Good API design goes beyond modeling the current state of resources and it is important to plan ahead how API evolves. For this it is essential to understand and document your user scenarios as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. We recommend creating a simple resource diagram, like the following, to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. ![Resource model example](ModelExample.png) After resources are defined it’s time to think about the behavior of your API which can be expressed via HTTP methods and operational resources such as functions and actions. As you think about API behavior you identify a happy path and various exceptions and deviations which will be expressed as errors and represented using HTTP codes and error messages. @@ -99,55 +100,49 @@ At every step of your design you need to consider security, privacy and complian ### Naming -API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand for developers. Ease of understanding comes from familiarity and recognition therefore when thinking about naming you should favor consistency with other Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). +API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand for developers. Ease of understanding comes from familiarity and recognition therefore when thinking about naming you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). -Below is a short summary of the most often used conventions. +Following is a short summary of the most often used conventions. -| Requirements | Example | -|---------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| :no_entry: **MUST NOT** use redundant words in names. |- **Right:** /places/{id}/**displayName** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeName* and /phones/{id}/**phoneNumber** | -| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | -| :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | -| :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong** addresses | -| :heavy_check_mark: **MUST** use plural nouns for collections (for listing a type or collection properties). | - **Right:** addresses
    - **Wrong** address | -| :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a "postpositive"). | - **Right:** passersby or mothersInLaw
    - **Wrong** notaryPublics or motherInLaws | +| Requirements | Example | +| ------------------------------------------------------------------------|-------------------------------| +| :no_entry: **MUST NOT** use redundant words in names. | - **Right:** /places/{id}/**displayName** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeName* and /phones/{id}/**phoneNumber** | +| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | +| :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | +| :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong** addresses | +| :heavy_check_mark: **MUST** use plural nouns for collections (for listing a type or collection properties). | - **Right:** addresses
    - **Wrong** address | +| :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a "postpositive").| - **Right:** passersby or mothersInLaw
    - **Wrong** notaryPublics or motherInLaws | | **casing** | | -| :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces | - **Right:** automaticRepliesStatus.
    - **Wrong** kebab-case or snake_case. | -| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | -| :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | -| :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate or crosswalk
    - **Wrong** metaData, semiCircle or airPlane | -| :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw or postOffice
    - **Wrong** paperclip or fullmoon | +| :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces | - **Right:** automaticRepliesStatus.
    - **Wrong** kebab-case or snake_case. | +| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | +| :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | +| :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate or crosswalk
    - **Wrong** metaData, semiCircle or airPlane | +| :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw or postOffice
    - **Wrong** paperclip or fullmoon | | **prefixes and suffixes** | | -| :heavy_check_mark: **MUST** suffix date and time properties with | - **Right:** dueDate — an Edm.Date
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Wrong** dueOn or startTime -
    - **Right:** instead both above are an Edm.DateTimeOffset | -| :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32 | -| :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong** enabledBool | -| :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl | -| :ballot_box_with_check: **SHOULD** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | -| :no_entry: **MUST NOT** use 'collection', 'response', 'request ' suffixes . |- **Right:** addresses
    - **Wrong** addressCollection | +| :heavy_check_mark: **MUST** suffix date and time properties with | - **Right:** dueDate — an Edm.Date
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Wrong** dueOn or startTime -
    - **Right:** instead both above are an Edm.DateTimeOffset | +| :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32 | +| :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong** enabledBool | +| :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl | +| :ballot_box_with_check: **SHOULD** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the following examples
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the preceding examples | +| :no_entry: **MUST NOT** use 'collection', 'response', 'request ' suffixes . |- **Right:** addresses
    - **Wrong** addressCollection | ### Uniform Resource Locators (URLs) -A Uniform Resource Locator (URL) is how developers access the resources of your -API. +A Uniform Resource Locator (URL) is how developers access the resources of your API. -Navigation paths to Microsoft Graph resources are generally broken into multiple -segments: +Navigation paths to Microsoft Graph resources are generally broken into multiple segments: -**{scheme}://{host}/{version}/{category}/[{pathSegment}][?{query}]** where +`{scheme}://{host}/{version}/{category}/[{pathSegment}][?{query}]` where: -- **scheme and host segments** are always - [https://graph.microsoft.com](https://graph.microsoft.com/v1.0/users); +- **scheme and host segments** are always `[https://graph.microsoft.com](https://graph.microsoft.com/v1.0/users)` -- **version** can be V1.0 or beta; +- **version** can be v1.0 or beta -- **category** segment is a logical grouping of APIs into top-level - categories; +- **category** segment is a logical grouping of APIs into top-level categories -- **pathSegment** is one or many navigation segments which can address an entity, - collection of entities, property or operation available for an entity +- **pathSegment** is one or many navigation segments that can address an entity, collection of entities, property, or operation available for an entity -- **query string** must follow the OData standard for query representations - and is covered in [Query](#query) section of OData specifications. +- **query string** must follow the OData standard for query representations and is covered in the Query section of OData specifications. While HTTP defines no constraints on how different resources are related together, it does encourage the use of URL path segment hierarchies to convey @@ -158,7 +153,7 @@ types and navigation properties. In Microsoft Graph a top-level API category may represent one of the following groupings: -1. A core *user-centric concept* of the Graph, i.e. /users, /groups or /me. +1. A core *user-centric concept* of Microsoft Graph, i.e. /users, /groups or /me. 2. A Microsoft *product or service offerings* covering multiple use cases, i.e. /teamwork, /directory. @@ -173,12 +168,12 @@ groupings: Effectively top-level categories define a perimeter for the API surface thus a new category creation requires additional rigor and governance approval. -### Query Support +### Query support Microsoft Graph APIs should support basic query options in conformance with -OData specifications and [Microsoft REST API -Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). -|Requirements| +OData specifications and [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). + +|Requirements | |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | | :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| @@ -191,15 +186,14 @@ The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or processing the request. One way to avoid this is to use the POST verb instead of GET with $query segment, and pass the query options part of the URL in the request body as described -in the chapter [OData Query -Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). +in the chapter [OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). -Another way is to use JSON batch as described in the [Microsoft Graph Documentation] (https://docs.microsoft.com/en-us/graph/json-batching#bypassing-url-length-limitations-with-batching). +Another way is to use JSON batch as described in the [Microsoft Graph documentation](https://docs.microsoft.com/graph/json-batching#bypassing-url-length-limitations-with-batching). -### Resource Modeling Patterns +### Resource modeling patterns You can model structured resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity type declares a key property to uniquely identify its objects and Complex Type does not. In Microsoft Graph this key property is called "id" for server-created key values. If there is a natural name for the key property then the workload can use that. -Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources, such as individually addressable items within a collection, for more information refer to [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. +Since objects of complex types on Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources, such as individually addressable items within a collection, for more information refer to [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. ```XML @@ -231,51 +225,43 @@ with multiple variants of a common concept. Type Hierarchy, Facets, and Flat bag of properties are three most often used patterns in Microsoft Graph today: - Type hierarchy is represented by one abstract base type with a few common - properties and one sub-type for each variant [Modelling with Subtypes - Pattern](./Modelling%20with%20Subtypes%20Pattern.md) + properties and one sub-type for each variant [Modelling with Subtypes Pattern](./patterns/subsets.md) - Facets are represented by a single entity type with common properties and one facet property (of complex type) per variant. The facet properties only - have a value when the object represents that variant [Modelling with Facets - Pattern](./Modelling%20with%20Facets%20Pattern.md) + have a value when the object represents that variant [Modelling with Facets Pattern](./patterns/facets.md) - Flat bag of properties is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required/meaningful for the variant given - by the type property. [Modelling with Flat Bag - Pattern](./Modelling%20with%20Flat%20Bag%20Pattern.md) - -The following table shows summary of main qualities for each pattern and will -help to select a pattern fit for your use case. - -| API qualities\
    Patterns | Properties and behavior
    described in metadata | Supports combinations
    of properties and behaviors | Simple query construction | -|---------------------------------------------------|-------------------------------------|-----------------------------------|---------------------------| -| Type hierarchy | yes | no | no | -| Facets | partially | yes | yes | -|Flat bag | no | no | yes | + by the type property. +The following table shows a summary of the main qualities for each pattern and will help you to select a pattern fit for your use case. +| API qualities\patterns | Properties and behavior described in metadata | Supports combinations of properties and behaviors | Simple query construction | +|-------------------------|-----------------------------------------------|---------------------------------------------------|---------------------------| +| Type hierarchy | yes | no | no | +| Facets | partially | yes | yes | +| Flat bag | no | no | yes | - -### Behavior Modeling +### Behavior modeling The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make. APIs SHOULD use resource-based designs with standard HTTP methods rather than operation resources if possible. - Operation resources are either functions or actions. According to [ODATA standards]( http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2) a function represents an operation which returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action may have side effects and may return a result represented as a single entity or collection of any type. + Operation resources are either functions or actions. According to [OData standards]( http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2) a function represents an operation which returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action may have side effects and may return a result represented as a single entity or collection of any type. | Microsoft Graph rules for modeling behavior | |-----------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections. This approach requires the server to produce system generated identities. | -| :heavy_check_mark: **MUST** use PATCH to edit updatable resources | -| :heavy_check_mark: **MUST** use DELETE to delete deletable resources | -| :heavy_check_mark: **MUST** use GET for listing and reading resources. | -| :warning: **SHOULD NOT** use PUT for updating resources. | -| :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical operation. | - +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.
    This approach requires the server to produce system generated identities. | +| :heavy_check_mark: **MUST** use PATCH to edit updatable resources. | +| :heavy_check_mark: **MUST** use DELETE to delete deletable resources. | +| :heavy_check_mark: **MUST** use GET for listing and reading resources. | +| :warning: **SHOULD NOT** use PUT for updating resources. | +| :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical operation. | Operation resources must have a binding parameter matching the type of the bound resource. In addition both actions and functions support overloading, meaning an API definition may contain multiple actions or functions with the same name. @@ -283,11 +269,9 @@ In addition both actions and functions support overloading, meaning an API defin For a complete list of standard HTTP operations you can refer to the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). -### Error Handling +### Error handling -Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should -follow when returning error condition responses. You can improve API traceability -and consistency by using recommended Graph error model and the Graph Utilities library to provide a standard implementation for your service : +Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should follow when returning error condition responses. You can improve API traceability and consistency by using the recommended Microsoft Graph error model and the Microsoft Graph Utilities library to provide a standard implementation for your service: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { @@ -340,8 +324,8 @@ The following examples demonstrate error modeling for common use cases: | Microsoft Graph enforces the following error rules | |-----------------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | -| :heavy_check_mark: **MUST** return a 403 Forbidden error when the application or signed-in user have insufficient permissions are present in the auth token. | +| :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | +| :heavy_check_mark: **MUST** return a 403 Forbidden error when the application or signed-in user have insufficient permissions are present in the auth token. | | :heavy_check_mark: **MUST** return a 429 Too Many Requests error when client exceeded throttling limits and 503 Service Unavailable when service overloaded but client is within throttling limits.| | :ballot_box_with_check: **SHOULD** return a 404 Not Found error if a 403 would result in information disclosure. | @@ -350,15 +334,12 @@ For a complete mapping of error codes to HTTP statuses you can refer to the ## API contract and non-backward compatible changes -Microsoft Graph definition of breaking changes is based on the [Microsoft REST -API -Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change). -In general, making all but additive changes to the API contract for existing elements is -considered breaking. Adding new elements is allowed and not considered a +Microsoft Graph definition of breaking changes is based on the +[Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change). +In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a breaking change. -\*\* Non-breaking changes:\*\* - +**Non-breaking changes:** - Addition of properties that are nullable or have a default value - Addition of a member, after the sentinel member, to an evolvable enumeration @@ -367,7 +348,7 @@ breaking change. - Changes to the length or format of opaque strings, such as resource IDs - Addition or removal of an annotation OpenType="true" -\*\* Breaking changes:\*\* +**Breaking changes:** - Changes to the URL or fundamental request/response associated with a resource @@ -382,58 +363,43 @@ breaking change. - Introduction of server-side pagination to existing collections - Significant changes to the performance of APIs such as increased latency, rate limits or concurrency. +### Versioning and deprecation - -### Versioning and Deprecation As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If that is not possible then you must version elements of your APIs. Microsoft Graph allows versioning of elements including entities and properties. Versioning involves adding a new, uniquely named version of the element and marking the old version as deprecated. In some cases, there will be a natural new name for the element. In other cases, where the original name is still the most descriptive, the suffix _v2 can be added to the original name to make it unique. The original element is then marked as deprecated using annotations. -Microsoft Graph provides two public endpoints to support API lifecycle: -1. API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in general availability (GA) status. -2. API sets on the beta endpoint (https://graph.microsoft.com/beta) are in beta or private preview status. +Microsoft Graph provides two public endpoints to support the API lifecycle: +- API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in general availability (GA) status. +- API sets on the beta endpoint (https://graph.microsoft.com/beta) are in beta or private preview status. Microsoft Graph APIs in the GA version guarantee API stability and consistency for its clients. If your API requires a breaking change in GA, then you MUST create new element versions and support deprecated elements for a minimum of 36 months or 24 months with demonstrated non-usage. On the beta endpoint breaking changes and deprecation of APIs are allowed with consideration of dependencies and customer impact. It is best practice to test new element versions on the beta endpoint at first then promote API changes to the GA endpoint. Detailed requirements for versioning and deprecation are described in the [Deprecation guidelines](./deprecation.md). +## Recommended API patterns -## Recommended API Patterns - -The guidelines in previous sections are intentionally brief and provide a jump -start for Graph API developers. More detailed design guidance on REST APIs is -published at the [Microsoft REST API -Guidelines](https://github.com/microsoft/api-guidelines/) and Graph specific -patterns are outlined in the table below. +The guidelines in previous sections are intentionally brief and provide a jump start for Microsoft Graph API developers. More detailed design guidance about REST APIs is published at the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). Microsoft Graph-specific patterns are outlined in the following table. Recommended API Design patterns: -| Pattern | Description | Reference | -|-------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| -| Type Hierarchy | The ability to model is-a relationships using subtypes. | [Subtypes](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | -| Facets | The ability to model parent-child relationships using Facet pattern. | [Facets](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | -| Dictionary | The ability for clients to provide an unknown quantity of data elements of the same type. | [Dictionary](./patterns/dictionary.md) | -| Evolvable Enums | The ability to extend enumerated types without breaking changes. | [Evolvable Enums](./patterns/evolvable-enums.md) | -| Change Tracking | The ability to keep API consumers in sync with changes without polling. | [Change Tracking](./patterns/change-tracking.md) | -| Namespace | The ability to organize resource definitions into a logical set. | [Namespace](./patterns/namespace.md) | -| Alternate Key | The ability to uniquely identify and query resources using an alternate key. | [Alternate Key](./patterns/alternate-key.md) | - - | - +| Pattern | Description | +|--------------------------------------------------|----------------------------------------------------------------------------| +| [Alternate key](./patterns/alternate-key.md) | Uniquely identify and query resources using an alternate key. | +| [Change tracking](./patterns/change-tracking.md) | Keep API consumers in sync with changes without polling. | +| [Dictionary](./patterns/dictionary.md) | Clients can provide an unknown quantity of data elements of the same type. | +| [Evolvable enums](./patterns/evolvable-enums.md) | Extend enumerated types without breaking changes. | +| [Facets](./patterns/facets.md) | Model parent-child relationships. | +| [Modeling subsets](./patterns/subsets.md) | Model collection subsets for All, None, Included, or Excluded criteria. | +| [Namespace](./patterns/namespace.md) | Organize resource definitions into a logical set. | +| [Type hierarchy](./patterns/subtypes.md) | Model `is-a` relationships using subtypes. | ## References -- [Microsoft REST API - Guidelines](https://github.com/microsoft/api-guidelines/) - -- [OData Guidelines](http://www.odata.org/documentation/) - -- [RESTful web API - design](https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design) - -- [Microsoft Graph - Documentation](https://developer.microsoft.com/en-us/graph/docs/concepts/overview) - -- [Microsoft Graph Explorer](https://aka.ms/ge) +- [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/) +- [OData guidelines](http://www.odata.org/documentation/) +- [RESTful web API design](https://docs.microsoft.com/azure/architecture/best-practices/api-design) +- [Microsoft Graph documentation](https://docs.microsoft.com/graph/overview) +- [Graph Explorer](https://developer.microsoft.com/graph/graph-explorer) From 87be0498a38ecfccc6f89619f9a67893405dad68 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Wed, 15 Jun 2022 09:13:59 -0700 Subject: [PATCH 402/729] Graph Guidelines: Edit pass part 2 - Introduction - Legend - Design approach - Naming --- graph/GuidelinesGraph.md | 105 +++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index dd0b2b90..92e75283 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -31,100 +31,99 @@ Table of contents ## Introduction -When building a digital ecosystem API usability becomes a business priority. Success of your ecosystem depends on APIs that are easy to discover, simple to use, fit for purpose, and consistent across your products. +When building a digital ecosystem API, usability becomes a business priority. The success of your ecosystem depends on APIs that are easy to discover, simple to use, fit for purpose, and consistent across your products. This document offers guidance that Microsoft Graph API producer teams MUST follow to -ensure that Microsoft Graph has a consistent and easy to use API surface. A new API design should meet the -following goals: +ensure that Microsoft Graph has a consistent and easy-to-use API surface. A new API design should meet the following goals: -\- Developer friendly via consistent naming, patterns, and web standards (HTTP, -REST, JSON) +- Be developer friendly by using consistent naming, patterns, and web standards (HTTP, REST, JSON). -\- Work well with SDKs in many programming languages. +- Work well with SDKs in many programming languages. -\- Sustainable & evolvable via clear API contracts. +- Be sustainable and evolvable by using clear API contracts. -The Microsoft Graph guidelines are an extension of the Microsoft REST API -guidelines. Readers are assumed also be reading and following the Microsoft REST API -guidelines except where this document outlines specific differences or exceptions to those guidelines. -Together these guidelines and a library of API patterns serve as the means by -which API teams discuss and come to consensus on API review requirements. +The Microsoft Graph REST API Guidelines are an extension of the +[Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). It is assumed that readers are following the Microsoft REST API Guidelines except where this document outlines specific differences or exceptions to those guidelines. Together, these guidelines and a library of API patterns serve as the means by which API teams discuss and come to consensus on API review requirements. Technology and software are constantly changing and evolving, and as such, this -is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface. Consequently, this document will more frequently change to add guidance in areas previously uncovered, or to clarify existing guidance. It will less frequently change the directional guidance it has already provided. -To suggest a change or propose a new idea, [open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose). +is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface. Consequently, this document will frequently change to add guidance in areas previously uncovered or to clarify existing guidance. It will less frequently change the directional guidance it has already provided. To suggest a change or propose a new idea, +[open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose). ### Legend This document offers prescriptive guidance labeled as follows: -:heavy_check_mark: **MUST** satisfy this specification. +:heavy_check_mark: **MUST** satisfy this specification. -:no_entry: **MUST NOT** use this pattern. +:no_entry: **MUST NOT** use this pattern. -:ballot_box_with_check: **SHOULD** fulfill this specification. +:ballot_box_with_check: **SHOULD** fulfill this specification. -:warning: **SHOULD NOT** adopt this pattern. +:warning: **SHOULD NOT** adopt this pattern. -If not following this advice, you MUST disclose your reasons during the Microsoft Graph API review. +If you do not follow this advice, you MUST disclose your reasons during the Microsoft Graph API review. ## Design approach -The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote API-first design approach where you begin your product design by focusing on how information will be exchanged and represented and creating an interface contract for your API which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface from your implementation and is essential for agility, predictability, and reuse of your APIs. Established interface contract allows developers to use your API while internal teams are still working on implementation; API specifications enable designing of user experience and test cases in parallel. Starting with user-facing contracts also promotes a good understanding of system interactions, your modeling domain, and understanding of how the service will evolve. +The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote an API-first design approach where you begin your product design by focusing on how information is exchanged and represented and by creating an interface contract for your API, which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface from your implementation and is essential for agility, predictability, and reuse of your APIs. -Microsoft Graph supports resource and query-based API styles that follow HTTP, REST, and JSON standards, where API contract is described using OData conventions and schema definition. For more information, see [Documentation · OData Version 4.01](https://www.odata.org/documentation/). +An established interface contract allows developers to use your API while internal teams are still working on implementation; API specifications enable designing user experience and test cases in parallel. Starting with user-facing contracts also promotes a good understanding of system interactions, your modeling domain, and an understanding of how the service will evolve. -In general API design includes the following steps: +Microsoft Graph supports resource and query-based API styles that follow HTTP, REST, and JSON standards, where the API contract is described by using OData conventions and schema definition. For more information, see [Documentation · OData Version 4.01](https://www.odata.org/documentation/). -- Outline the key current and future scenarios for API consumers +In general, API design includes the following steps: + +1. Outline the key current and future scenarios for API consumers. -- Define your domain model +1. Define your domain model. -- Derive and name your API resources +1. Derive and name your API resources. -- Describe relationships between resources +1. Describe relationships between resources. -- Determine required behavior +1. Determine required behavior. -- Determine user roles and application permissions +1. Determine user roles and application permissions. -- Specify errors +1. Specify errors. -When creating your API contract you will define resources based on the domain model supporting your service and identify interactions based on user scenarios. Good API design goes beyond modeling the current state of resources and it is important to plan ahead how API evolves. For this it is essential to understand and document your user scenarios as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. We recommend creating a simple resource diagram, like the following, to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. -![Resource model example](ModelExample.png) +When creating your API contract, you define resources based on the domain model supporting your service and identify interactions based on user scenarios. Good API design goes beyond modeling the current state of resources. It is important to plan ahead how the API evolves; to do this, it is essential to understand and document your user scenarios as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. -After resources are defined it’s time to think about the behavior of your API which can be expressed via HTTP methods and operational resources such as functions and actions. As you think about API behavior you identify a happy path and various exceptions and deviations which will be expressed as errors and represented using HTTP codes and error messages. +We recommend creating a simple resource diagram like the following to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. + +![Resource model example](ModelExample.png) -At every step of your design you need to consider security, privacy and compliance as intrinsic components of your API implementation. +After you define your resources, it’s time to think about the behavior of your API, which can be expressed via HTTP methods and operational resources such as functions and actions. As you think about API behavior, you identify a happy path and various exceptions and deviations that will be expressed as errors and represented by using HTTP codes and error messages. +At every step of your design, you need to consider security, privacy, and compliance as intrinsic components of your API implementation. ### Naming -API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand for developers. Ease of understanding comes from familiarity and recognition therefore when thinking about naming you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). +API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). Following is a short summary of the most often used conventions. | Requirements | Example | | ------------------------------------------------------------------------|-------------------------------| -| :no_entry: **MUST NOT** use redundant words in names. | - **Right:** /places/{id}/**displayName** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeName* and /phones/{id}/**phoneNumber** | -| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | -| :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | -| :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong** addresses | -| :heavy_check_mark: **MUST** use plural nouns for collections (for listing a type or collection properties). | - **Right:** addresses
    - **Wrong** address | -| :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a "postpositive").| - **Right:** passersby or mothersInLaw
    - **Wrong** notaryPublics or motherInLaws | -| **casing** | | -| :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces | - **Right:** automaticRepliesStatus.
    - **Wrong** kebab-case or snake_case. | -| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | -| :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | -| :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate or crosswalk
    - **Wrong** metaData, semiCircle or airPlane | -| :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw or postOffice
    - **Wrong** paperclip or fullmoon | -| **prefixes and suffixes** | | -| :heavy_check_mark: **MUST** suffix date and time properties with | - **Right:** dueDate — an Edm.Date
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Wrong** dueOn or startTime -
    - **Right:** instead both above are an Edm.DateTimeOffset | -| :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32 | -| :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong** enabledBool | -| :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl | -| :ballot_box_with_check: **SHOULD** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the following examples
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the preceding examples | -| :no_entry: **MUST NOT** use 'collection', 'response', 'request ' suffixes . |- **Right:** addresses
    - **Wrong** addressCollection | +| :no_entry: **MUST NOT** use redundant words in names. | - **Right:** /places/{id}/**displayName** or /phones/{id}/**number**
    - **Wrong:** /places/{id}/**placeName** or /phones/{id}/**phoneNumber** | +| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong:** teamsChat | +| :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong:** msodsUrl or dlp | +| :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong:** addresses | +| :heavy_check_mark: **MUST** use plural nouns for collections (for listing type or collection properties). | - **Right:** addresses
    - **Wrong:** address | +| :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a *postpositive*).| - **Right:** passersby or mothersInLaw
    - **Wrong:** notaryPublics or motherInLaws | +| **CASING** | | +| :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces. | - **Right:** automaticRepliesStatus
    - **Wrong:** kebab-case or snake_case | +| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong:** iOLimit or totalIoAmount | +| :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong:** webHTML | +| :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate, or crosswalk
    - **Wrong:** metaData, semiCircle, or airPlane | +| :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw, or postOffice
    - **Wrong:** paperclip or fullmoon | +| **PREFIXES AND SUFFIXES** | | +| :heavy_check_mark: **MUST** suffix date and time properties with Date, Time, or DateTime | - **Right:** dueDate — an Edm.Date
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Wrong:** dueOn or startTime
    - **Right:** Instead, both of the preceding are an Edm.DateTimeOffset | +| :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an `int`, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (use of Edm.Duration type is preferable)
    - **Wrong:** passwordValidityPeriod — an Edm.Int32 | +| :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong:** enabledBool | +| :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem or userId on auditActor
    - **Wrong:** webUrl on contact when it's the companyWebUrl | +| :ballot_box_with_check: **SHOULD** prefix Boolean properties with `is`, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong:** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom (subjectively more natural than the following examples)
    - **Wrong:** isNewTimeProposalsAllowed or isInvitesFromAllowed (subjectively more awkward that the preceding examples) | +| :no_entry: **MUST NOT** use collection, response, or request suffixes. | - **Right:** addresses
    - **Wrong:** addressCollection | ### Uniform Resource Locators (URLs) From 3d0981e4cd4bea31d1aa6bc78b715681fd3481e6 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Wed, 15 Jun 2022 10:55:58 -0700 Subject: [PATCH 403/729] Graph Guidelines: Edit pass part 3 Finished remaining sections. --- graph/GuidelinesGraph.md | 229 +++++++++++++++++---------------------- 1 file changed, 98 insertions(+), 131 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 92e75283..8cc2caab 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -129,72 +129,60 @@ Following is a short summary of the most often used conventions. A Uniform Resource Locator (URL) is how developers access the resources of your API. -Navigation paths to Microsoft Graph resources are generally broken into multiple segments: - +Navigation paths to Microsoft Graph resources are generally broken into multiple segments, `{scheme}://{host}/{version}/{category}/[{pathSegment}][?{query}]` where: -- **scheme and host segments** are always `[https://graph.microsoft.com](https://graph.microsoft.com/v1.0/users)` +- **scheme and host segments** are always [`https://graph.microsoft.com`](https://graph.microsoft.com/v1.0/users). -- **version** can be v1.0 or beta +- **version** can be v1.0 or beta. -- **category** segment is a logical grouping of APIs into top-level categories +- **category** segment is a logical grouping of APIs into top-level categories. -- **pathSegment** is one or many navigation segments that can address an entity, collection of entities, property, or operation available for an entity +- **pathSegment** is one or many navigation segments that can address an entity, collection of entities, property, or operation available for an entity. - **query string** must follow the OData standard for query representations and is covered in the Query section of OData specifications. -While HTTP defines no constraints on how different resources are related -together, it does encourage the use of URL path segment hierarchies to convey -relationships. In Microsoft Graph relationships between resources are -supported by the OData concepts of singletons, entitySets, entities, complex -types and navigation properties. +While HTTP defines no constraints on how different resources are related, it does encourage the use of URL path segment hierarchies to convey relationships. In Microsoft Graph, relationships between resources are supported by the OData concepts of singletons, entitySets, entities, complex types, and navigation properties. -In Microsoft Graph a top-level API category may represent one of the following -groupings: +In Microsoft Graph, a top-level API category might represent one of the following groupings: -1. A core *user-centric concept* of Microsoft Graph, i.e. /users, /groups or /me. +- A core *user-centric concept* of Microsoft Graph: /users, /groups, or /me. -2. A Microsoft *product or service offerings* covering multiple use cases, i.e. /teamwork, /directory. +- A Microsoft *product or service offering* covering multiple use cases: /teamwork, /directory. -3. A *feature offering* covering a single use case and *shared* across multiple - Microsoft products, i.e. /search, /notifications, /subscriptions. +- A *feature offering* covering a single use case and *shared* across multiple Microsoft products: /search, /notifications, /subscriptions. -4. *Administrative configuration* functions for specific products. i.e. /admin/exchange. +- *Administrative configuration* functions for specific products: /admin/exchange. -5. Internal Microsoft requirements for publishing Privileged and Hidden APIs, - routing, and load testing, i.e./loadTestEntities. +- Internal Microsoft requirements for publishing Privileged and Hidden APIs, routing, and load testing: /loadTestEntities. -Effectively top-level categories define a perimeter for the API surface thus a -new category creation requires additional rigor and governance approval. +Effectively, top-level categories define a perimeter for the API surface; thus, a new category creation requires additional rigor and governance approval. ### Query support -Microsoft Graph APIs should support basic query options in conformance with -OData specifications and [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). +Microsoft Graph APIs should support basic query options in conformance with OData specifications and [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). |Requirements | |----------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | -| :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| -| :heavy_check_mark: **MUST** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL) | -| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using $top and $skip (or $skipToken)"| -| :ballot_box_with_check: **SHOULD** support $count for collections | -| :ballot_box_with_check: **SHOULD** sorting with \$orderby both ascending and descending on properties of the entities | +| :heavy_check_mark: **MUST** support `$select on resource` to enable properties projection. | +| :ballot_box_with_check: **SHOULD** support `\$filter with eq`, `ne` operations on properties of entities for collections. | +| :heavy_check_mark: **MUST** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | +| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip` (or `$skipToken`). | +| :ballot_box_with_check: **SHOULD** support `$count` for collections. | +| :ballot_box_with_check: **SHOULD** support sorting with `$orderby` both ascending and descending on properties of the entities. | -The query options part of an OData URL can be quite long, potentially exceeding -the maximum length of URLs supported by components involved in transmitting or -processing the request. One way to avoid this is to use the POST verb instead of -GET with $query segment, and pass the query options part of the URL in the request body as described -in the chapter [OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). +The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or processing the request. One way to avoid this is to use the POST verb instead of GET with the `$query` segment, and pass the query options part of the URL in the request body as described in the chapter +[OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). -Another way is to use JSON batch as described in the [Microsoft Graph documentation](https://docs.microsoft.com/graph/json-batching#bypassing-url-length-limitations-with-batching). +Another way to avoid this is to use JSON batch as described in the [Microsoft Graph documentation](https://docs.microsoft.com/graph/json-batching#bypassing-url-length-limitations-with-batching). ### Resource modeling patterns -You can model structured resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity type declares a key property to uniquely identify its objects and Complex Type does not. In Microsoft Graph this key property is called "id" for server-created key values. If there is a natural name for the key property then the workload can use that. -Since objects of complex types on Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources, such as individually addressable items within a collection, for more information refer to [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. +You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type does not. In Microsoft Graph, this key property is called `id` for server-created key values. If there is a natural name for the key property, then the workload can use that. + +Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must not use complex type to model addressable resources such as individually addressable items within a collection. For more information, see the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. -```XML +```xml @@ -210,69 +198,57 @@ Since objects of complex types on Microsoft Graph don’t have unique identifier
    ``` -| Microsoft Graph rules for modeling complex resources | -|----------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use String type for id | -| :heavy_check_mark: **MUST** use a primary key composed of a single property | -| :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | -| :heavy_check_mark: **MUST** use a root object with a value property to return a collection | -| :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | -| :warning: **SHOULD NOT** add the property id to a complex type | - -There are different approaches for designing an API resource model in situations -with multiple variants of a common concept. Type Hierarchy, Facets, and Flat bag -of properties are three most often used patterns in Microsoft Graph today: - -- Type hierarchy is represented by one abstract base type with a few common - properties and one sub-type for each variant [Modelling with Subtypes Pattern](./patterns/subsets.md) - -- Facets are represented by a single entity type with common properties and - one facet property (of complex type) per variant. The facet properties only - have a value when the object represents that variant [Modelling with Facets Pattern](./patterns/facets.md) - -- Flat bag of properties is represented by one entity type with all the - potential properties plus an additional property to distinguish the - variants, often called type. The type property describes the variant and - also defines properties that are required/meaningful for the variant given - by the type property. - -The following table shows a summary of the main qualities for each pattern and will help you to select a pattern fit for your use case. + +| Microsoft Graph rules for modeling complex resources | +|---------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** use String type for ID. | +| :heavy_check_mark: **MUST** use a primary key composed of a single property. | +| :heavy_check_mark: **MUST** use an object as the root of all JSON payloads. | +| :heavy_check_mark: **MUST** use a root object with a value property to return a collection. | +| :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous. | +| :warning: **SHOULD NOT** add the property ID to a complex type. | + +There are different approaches for designing an API resource model in situations with multiple variants of a common concept. +The three most often used patterns in Microsoft Graph today are type hierarchy, facets, and flat bag of properties: + +- [Type hierarchy](./patterns/subtypes.md) is represented by one abstract base type with a few common properties and one subtype for each variant. + +- [Facets](./patterns/facets.md) are represented by a single entity type with common properties and one facet property (of complex type) per variant. The facet properties only have a value when the object represents that variant. + +- Flat bag of properties is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required or meaningful for the variant given by the type property. + +The following table shows a summary of the main qualities for each pattern and can help you select a pattern fit for your use case. | API qualities\patterns | Properties and behavior described in metadata | Supports combinations of properties and behaviors | Simple query construction | |-------------------------|-----------------------------------------------|---------------------------------------------------|---------------------------| | Type hierarchy | yes | no | no | | Facets | partially | yes | yes | -| Flat bag | no | no | yes | +| Flat bag of properties | no | no | yes | ### Behavior modeling -The HTTP operations dictate how your API behaves. The URL of an API, along with -its request/response bodies, establishes the overall contract that developers -have with your service. As an API provider, how you manage the overall request / -response pattern should be one of the first implementation decisions you make. -APIs SHOULD use resource-based designs with standard HTTP methods rather than operation resources if possible. - Operation resources are either functions or actions. According to [OData standards]( http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2) a function represents an operation which returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action may have side effects and may return a result represented as a single entity or collection of any type. +The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request/response pattern should be one of the first implementation decisions you make. + +If possible, APIs SHOULD use resource-based designs with standard HTTP methods rather than operation resources. Operation resources are either functions or actions. According to [OData standards](http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2), a function represents an operation that returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action might have side effects and might return a result represented as a single entity or collection of any type. -| Microsoft Graph rules for modeling behavior | -|-----------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.
    This approach requires the server to produce system generated identities. | +| Microsoft Graph rules for modeling behavior | +|------------------------------------------------------------------| +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.
    This approach requires the server to produce system generated identities. | | :heavy_check_mark: **MUST** use PATCH to edit updatable resources. | | :heavy_check_mark: **MUST** use DELETE to delete deletable resources. | | :heavy_check_mark: **MUST** use GET for listing and reading resources. | | :warning: **SHOULD NOT** use PUT for updating resources. | | :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical operation. | -Operation resources must have a binding parameter matching the type of the bound resource. -In addition both actions and functions support overloading, meaning an API definition may contain multiple actions or functions with the same name. +Operation resources must have a binding parameter that matches the type of the bound resource. In addition, both actions and functions support overloading, meaning an API definition might contain multiple actions or functions with the same name. -For a complete list of standard HTTP operations you can refer to the [Microsoft -REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). +For a complete list of standard HTTP operations, see the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). ### Error handling Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should follow when returning error condition responses. You can improve API traceability and consistency by using the recommended Microsoft Graph error model and the Microsoft Graph Utilities library to provide a standard implementation for your service: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +```http { "error": { "code": "BadRequest", @@ -284,16 +260,13 @@ Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs shoul } } } -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` -The top-level error code must be aligned with HTTP response status codes according to [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). -The following examples demonstrate error modeling for common use cases: +The top-level error code must be aligned with HTTP response status codes according to [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). The following examples demonstrate error modeling for common use cases: -- **Simple error**: An API wants to report an error with top-level details - only. Then the error object contains the top-level error code, message and - target (optional). +- **Simple error**: An API wants to report an error with top-level details only. The error object contains the top-level error code, message and target (optional). -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +```http { "error": { "code": "BadRequest", @@ -301,14 +274,11 @@ The following examples demonstrate error modeling for common use cases: "target": "Resource X (Optional)" } } -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` -- **Detailed error**: An API needs to provide service-specific details of the - error via the innererror property of the error object. It is intended to allow - services to supply a specific error code to help differentiate errors that - share the same top-level error code but reported for different reasons. +- **Detailed error**: An API needs to provide service-specific details of the error via the innererror property of the error object. It is intended to allow services to supply a specific error code to help differentiate errors that share the same top-level error code but are reported for different reasons. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +```http { "error": { "code": "BadRequest", @@ -319,63 +289,60 @@ The following examples demonstrate error modeling for common use cases: } } } -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` -| Microsoft Graph enforces the following error rules | -|-----------------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | -| :heavy_check_mark: **MUST** return a 403 Forbidden error when the application or signed-in user have insufficient permissions are present in the auth token. | -| :heavy_check_mark: **MUST** return a 429 Too Many Requests error when client exceeded throttling limits and 503 Service Unavailable when service overloaded but client is within throttling limits.| +| Microsoft Graph enforces the following error rules | +|-------------------------------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | +| :heavy_check_mark: **MUST** return a 403 Forbidden error when the application or signed-in user has insufficient permissions present in the auth token. | +| :heavy_check_mark: **MUST** return a 429 Too Many Requests error when the client exceeded throttling limits, and a 503 Service Unavailable error when the service overloaded but the client is within throttling limits.| | :ballot_box_with_check: **SHOULD** return a 404 Not Found error if a 403 would result in information disclosure. | -For a complete mapping of error codes to HTTP statuses you can refer to the +For a complete mapping of error codes to HTTP statuses, see [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). ## API contract and non-backward compatible changes -Microsoft Graph definition of breaking changes is based on the -[Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change). -In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a -breaking change. +The Microsoft Graph definition of breaking changes is based on the +[Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change). In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a breaking change. **Non-breaking changes:** -- Addition of properties that are nullable or have a default value -- Addition of a member, after the sentinel member, to an evolvable enumeration -- Removal, rename, or change to the type of an annotation -- Changes to the order of properties -- Changes to the length or format of opaque strings, such as resource IDs -- Addition or removal of an annotation OpenType="true" +- Adding properties that are nullable or have a default value +- Adding a member after the sentinel member to an evolvable enumeration +- Removing, renaming, or changing the type of annotation +- Changing the order of properties +- Changing the length or format of opaque strings, such as resource IDs +- Adding or removing an annotation OpenType="true" **Breaking changes:** -- Changes to the URL or fundamental request/response associated with a - resource -- Removal, rename, or change to an incompatible type of a declared property -- Removal or rename of APIs or API parameters -- Addition of a required request header -- Addition of a EnumType members for non-evolvable enumerations -- Addition of a Nullable="false" properties to existing types -- Addition of a parameter not marked as [Nullable](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Nullable) to existing actions -- Addition of a parameter not marked as [Optional](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md#OptionalParameter) to an existing function -- Changes to top-level error codes -- Introduction of server-side pagination to existing collections -- Significant changes to the performance of APIs such as increased latency, rate limits or concurrency. +- Changing the URL or fundamental request/response associated with a resource +- Removing, renaming, or changing an incompatible type of a declared property +- Removing or renaming APIs or API parameters +- Adding a required request header +- Adding EnumType members for non-evolvable enumerations +- Adding Nullable="false" properties to existing types +- Adding a parameter not marked as [Nullable](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Nullable) to existing actions +- Adding a parameter not marked as [Optional](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md#OptionalParameter) to an existing function +- Changing top-level error codes +- Introducing server-side pagination to existing collections +- Making significant changes to the performance of APIs such as increased latency, rate limits, or concurrency ### Versioning and deprecation -As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If that is not possible then you must version elements of your APIs. -Microsoft Graph allows versioning of elements including entities and properties. Versioning involves adding a new, uniquely named version of the element and marking the old version as deprecated. +As the market and technology evolves, your APIs will require modifications. In this case, you must avoid breaking changes and add new resources and features incrementally. If that is not possible, then you must version elements of your APIs. Microsoft Graph allows versioning of elements, including entities and properties. Versioning involves adding a new, uniquely named version of the element and marking the old version as deprecated. -In some cases, there will be a natural new name for the element. In other cases, where the original name is still the most descriptive, the suffix _v2 can be added to the original name to make it unique. -The original element is then marked as deprecated using annotations. +In some cases, there is a natural new name for the element. In other cases, where the original name is still the most descriptive, the suffix _v2 can be added to the original name to make it unique. The original element is then marked as deprecated by using annotations. Microsoft Graph provides two public endpoints to support the API lifecycle: -- API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in general availability (GA) status. -- API sets on the beta endpoint (https://graph.microsoft.com/beta) are in beta or private preview status. +- [API sets on the v1.0 endpoint](https://graph.microsoft.com/v1.0) are in general availability (GA) status. +- [API sets on the beta endpoint](https://graph.microsoft.com/beta) are in beta or private preview status. + +Microsoft Graph APIs in the GA version guarantee API stability and consistency for its clients. If your API requires a breaking change in GA, then you MUST create new element versions and support deprecated elements for a minimum of 36 months or 24 months with demonstrated non-usage. + +On the beta endpoint, breaking changes and deprecation of APIs are allowed with consideration of dependencies and customer impact. It is a best practice to test new element versions on the beta endpoint at first and then promote API changes to the GA endpoint. -Microsoft Graph APIs in the GA version guarantee API stability and consistency for its clients. If your API requires a breaking change in GA, then you MUST create new element versions and support deprecated elements for a minimum of 36 months or 24 months with demonstrated non-usage. -On the beta endpoint breaking changes and deprecation of APIs are allowed with consideration of dependencies and customer impact. It is best practice to test new element versions on the beta endpoint at first then promote API changes to the GA endpoint. Detailed requirements for versioning and deprecation are described in the [Deprecation guidelines](./deprecation.md). ## Recommended API patterns From cfab7ac0ddd006511803b014989fadadb2007585 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Wed, 15 Jun 2022 12:16:25 -0700 Subject: [PATCH 404/729] Final edits --- graph/GuidelinesGraph.md | 76 +++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 8cc2caab..85a8c72f 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -16,7 +16,7 @@ Table of contents - [Error handling](#error-handling) - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) - [Versioning and deprecation](#versioning-and-deprecation) - - [Recommended API patterns](#recommended-api-patterns) + - [Recommended API design patterns](#recommended-api-design-patterns) - [References](#references) ## @@ -69,7 +69,7 @@ The design of your API is arguably the most important investment you will make. An established interface contract allows developers to use your API while internal teams are still working on implementation; API specifications enable designing user experience and test cases in parallel. Starting with user-facing contracts also promotes a good understanding of system interactions, your modeling domain, and an understanding of how the service will evolve. -Microsoft Graph supports resource and query-based API styles that follow HTTP, REST, and JSON standards, where the API contract is described by using OData conventions and schema definition. For more information, see [Documentation · OData Version 4.01](https://www.odata.org/documentation/). +Microsoft Graph supports resource and query-based API styles that follow HTTP, REST, and JSON standards, where the API contract is described by using OData conventions and schema definitions. For more information, see [Documentation · OData Version 4.01](https://www.odata.org/documentation/). In general, API design includes the following steps: @@ -99,7 +99,7 @@ At every step of your design, you need to consider security, privacy, and compli ### Naming -API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). +API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API naming guidelines)](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#17-naming-guidelines). Following is a short summary of the most often used conventions. @@ -132,15 +132,15 @@ A Uniform Resource Locator (URL) is how developers access the resources of your Navigation paths to Microsoft Graph resources are generally broken into multiple segments, `{scheme}://{host}/{version}/{category}/[{pathSegment}][?{query}]` where: -- **scheme and host segments** are always [`https://graph.microsoft.com`](https://graph.microsoft.com/v1.0/users). +- `scheme` and `host` segments are always [`https://graph.microsoft.com`](https://graph.microsoft.com/v1.0/users). -- **version** can be v1.0 or beta. +- `version` can be v1.0 or beta. -- **category** segment is a logical grouping of APIs into top-level categories. +- `category` is a logical grouping of APIs into top-level categories. -- **pathSegment** is one or many navigation segments that can address an entity, collection of entities, property, or operation available for an entity. +- `pathSegment` is one or many navigation segments that can address an entity, collection of entities, property, or operation available for an entity. -- **query string** must follow the OData standard for query representations and is covered in the Query section of OData specifications. +- `query` string must follow the OData standard for query representations and is covered in the Query section of OData specifications. While HTTP defines no constraints on how different resources are related, it does encourage the use of URL path segment hierarchies to convey relationships. In Microsoft Graph, relationships between resources are supported by the OData concepts of singletons, entitySets, entities, complex types, and navigation properties. @@ -160,7 +160,7 @@ Effectively, top-level categories define a perimeter for the API surface; thus, ### Query support -Microsoft Graph APIs should support basic query options in conformance with OData specifications and [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). +Microsoft Graph APIs should support basic query options in conformance with OData specifications and [Microsoft REST API Guidelines for error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses). |Requirements | |----------------------------------------------------------------------------------------------------| @@ -174,13 +174,13 @@ Microsoft Graph APIs should support basic query options in conformance with ODat The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or processing the request. One way to avoid this is to use the POST verb instead of GET with the `$query` segment, and pass the query options part of the URL in the request body as described in the chapter [OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). -Another way to avoid this is to use JSON batch as described in the [Microsoft Graph documentation](https://docs.microsoft.com/graph/json-batching#bypassing-url-length-limitations-with-batching). +Another way to avoid this is to use JSON batch as described in the [Microsoft Graph batching documentation](https://docs.microsoft.com/graph/json-batching#bypassing-url-length-limitations-with-batching). ### Resource modeling patterns You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type does not. In Microsoft Graph, this key property is called `id` for server-created key values. If there is a natural name for the key property, then the workload can use that. -Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must not use complex type to model addressable resources such as individually addressable items within a collection. For more information, see the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. +Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must not use complex type to model addressable resources such as individually addressable items within a collection. For more information, see the [Microsoft REST API Guidelines collection URL patterns](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. ```xml @@ -242,11 +242,11 @@ If possible, APIs SHOULD use resource-based designs with standard HTTP methods r Operation resources must have a binding parameter that matches the type of the bound resource. In addition, both actions and functions support overloading, meaning an API definition might contain multiple actions or functions with the same name. -For a complete list of standard HTTP operations, see the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). +For a complete list of standard HTTP operations, see the [Microsoft REST API Guidelines error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses). ### Error handling -Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should follow when returning error condition responses. You can improve API traceability and consistency by using the recommended Microsoft Graph error model and the Microsoft Graph Utilities library to provide a standard implementation for your service: +Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should follow when returning error condition responses. You can improve API traceability and consistency by using the recommended Microsoft Graph error model and the Microsoft Graph utilities library to provide a standard implementation for your service: ```http { @@ -266,37 +266,37 @@ The top-level error code must be aligned with HTTP response status codes accordi - **Simple error**: An API wants to report an error with top-level details only. The error object contains the top-level error code, message and target (optional). -```http -{ - "error": { - "code": "BadRequest", - "message": "Cannot process the request because it is malformed or incorrect.", - "target": "Resource X (Optional)" - } -} -``` + ```http + { + "error": { + "code": "BadRequest", + "message": "Cannot process the request because it is malformed or incorrect.", + "target": "Resource X (Optional)" + } + } + ``` - **Detailed error**: An API needs to provide service-specific details of the error via the innererror property of the error object. It is intended to allow services to supply a specific error code to help differentiate errors that share the same top-level error code but are reported for different reasons. - -```http -{ - "error": { - "code": "BadRequest", - "message": "Cannot process the request because it is malformed or incorrect.", - "innererror": { - "code": "requiredFieldOrParameterMissing", - + + ```http + { + "error": { + "code": "BadRequest", + "message": "Cannot process the request because it is malformed or incorrect.", + "innererror": { + "code": "requiredFieldOrParameterMissing", + + } + } } - } -} -``` + ``` | Microsoft Graph enforces the following error rules | |-------------------------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | | :heavy_check_mark: **MUST** return a 403 Forbidden error when the application or signed-in user has insufficient permissions present in the auth token. | | :heavy_check_mark: **MUST** return a 429 Too Many Requests error when the client exceeded throttling limits, and a 503 Service Unavailable error when the service overloaded but the client is within throttling limits.| -| :ballot_box_with_check: **SHOULD** return a 404 Not Found error if a 403 would result in information disclosure. | +| :ballot_box_with_check: **SHOULD** return a 404 Not Found error if a 403 error would result in information disclosure. | For a complete mapping of error codes to HTTP statuses, see [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). @@ -304,7 +304,7 @@ For a complete mapping of error codes to HTTP statuses, see ## API contract and non-backward compatible changes The Microsoft Graph definition of breaking changes is based on the -[Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change). In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a breaking change. +[Microsoft REST API Guidelines definition of a breaking change](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#123-definition-of-a-breaking-change). In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a breaking change. **Non-breaking changes:** @@ -345,12 +345,10 @@ On the beta endpoint, breaking changes and deprecation of APIs are allowed with Detailed requirements for versioning and deprecation are described in the [Deprecation guidelines](./deprecation.md). -## Recommended API patterns +## Recommended API design patterns The guidelines in previous sections are intentionally brief and provide a jump start for Microsoft Graph API developers. More detailed design guidance about REST APIs is published at the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). Microsoft Graph-specific patterns are outlined in the following table. -Recommended API Design patterns: - | Pattern | Description | |--------------------------------------------------|----------------------------------------------------------------------------| | [Alternate key](./patterns/alternate-key.md) | Uniquely identify and query resources using an alternate key. | From 8978636bbe02b4ede41dca24474b5d0389feeaaa Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Wed, 15 Jun 2022 12:28:51 -0700 Subject: [PATCH 405/729] Fixed two typos --- graph/GuidelinesGraph.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 85a8c72f..d8201d38 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -99,7 +99,7 @@ At every step of your design, you need to consider security, privacy, and compli ### Naming -API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API naming guidelines)](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#17-naming-guidelines). +API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API naming guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#17-naming-guidelines). Following is a short summary of the most often used conventions. @@ -142,7 +142,7 @@ Navigation paths to Microsoft Graph resources are generally broken into multiple - `query` string must follow the OData standard for query representations and is covered in the Query section of OData specifications. -While HTTP defines no constraints on how different resources are related, it does encourage the use of URL path segment hierarchies to convey relationships. In Microsoft Graph, relationships between resources are supported by the OData concepts of singletons, entitySets, entities, complex types, and navigation properties. +While HTTP defines no constraints on how different resources are related, it does encourage the use of URL path segment hierarchies to convey relationships. In Microsoft Graph, relationships between resources are supported by the OData concepts of singletons, entity sets, entities, complex types, and navigation properties. In Microsoft Graph, a top-level API category might represent one of the following groupings: From 1d68552184084eddb491dc901134a6989ee7c0ee Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Thu, 16 Jun 2022 17:55:30 -0700 Subject: [PATCH 406/729] Modified URL Guidelines for DNS Done Right --- azure/Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 70cd6ac8..475892f6 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -66,8 +66,8 @@ https://...// Where: | Field | Description | - | - | - | tenant | Regionally-unique ID representing tenant. Used for isolation, billing, quota enforcement, lifetime of resources, etc. - | region | A code (see the table below) identifying the tenant's selected region. + | tenant | Regionally-unique ID representing a tenant (used for isolation, billing, quota enforcement, lifetime of resources, etc.) + | region | Identifies the tenant's selected region. This region string MUST match one of the strings in the "Name" column returned from running this Azure CLI's "az account list-locations -o table" | service | Name of the service (ex: blobstore, servicebus, directory, or management) | cloud | Cloud domain name, e.g. `azure.net` (see Azure CLI's "az cloud list") | service‑root | Service-specific path (ex: blobcontainer, myqueue) From 2cebfbdb363a291596c9908752ca7e03daace2dd Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Thu, 16 Jun 2022 17:57:17 -0700 Subject: [PATCH 407/729] Update Guidelines.md --- azure/Guidelines.md | 1 + 1 file changed, 1 insertion(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 475892f6..98d8bb0e 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -3,6 +3,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2022-Jun-16 | Updated URL guidelines for DNS Done Right | | 2022-May-11 | Drop guidance on version discovery | | 2022-Mar-29 | Add guidelines about using durations | | 2022-Mar-25 | Update guideline for date values in headers to follow RFC 7231 | From 6d0a73a807703812b65b62b9d61f2eb0a6a1fc5b Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Fri, 24 Jun 2022 14:18:14 -0700 Subject: [PATCH 408/729] Moved Pros and cons section from wiki and edited --- graph/GuidelinesGraph.md | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index d8201d38..26282ca0 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -211,11 +211,11 @@ Because objects of complex types in Microsoft Graph don’t have unique identifi There are different approaches for designing an API resource model in situations with multiple variants of a common concept. The three most often used patterns in Microsoft Graph today are type hierarchy, facets, and flat bag of properties: -- [Type hierarchy](./patterns/subtypes.md) is represented by one abstract base type with a few common properties and one subtype for each variant. +- **[Type hierarchy](./patterns/subtypes.md)** is represented by one abstract base type with a few common properties and one subtype for each variant. -- [Facets](./patterns/facets.md) are represented by a single entity type with common properties and one facet property (of complex type) per variant. The facet properties only have a value when the object represents that variant. +- **[Facets](./patterns/facets.md)** are represented by a single entity type with common properties and one facet property (of complex type) per variant. The facet properties only have a value when the object represents that variant. -- Flat bag of properties is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required or meaningful for the variant given by the type property. +- **Flat bag of properties** is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required or meaningful for the variant given by the type property. The following table shows a summary of the main qualities for each pattern and can help you select a pattern fit for your use case. @@ -223,7 +223,32 @@ The following table shows a summary of the main qualities for each pattern and c |-------------------------|-----------------------------------------------|---------------------------------------------------|---------------------------| | Type hierarchy | yes | no | no | | Facets | partially | yes | yes | -| Flat bag of properties | no | no | yes | +| Flat | no | no | yes | + +#### Pros and cons + +Following are a few pros and cons to decide which pattern to use: + +- In **[hierarchy](./patterns/subtypes.md)**, the interdependencies of properties, that is, which properties are relevant for which variants, is fully captured in metadata, and client code can potentially leverage that to construct and/or validate requests. + +- Introducing new cases in **hierarchy** is relatively isolated (which is why it is so familiar to OOP) and is considered backwards compatible (at least syntactically). + +- Introducing new cases/variants in **[facets](./patterns/facets.md)** is straightforward. You need to be careful because it can introduce situations where previously only one of the facets was non-null and now all the old ones are null. This is not unlike adding new subtypes in the **hierarchy** pattern or adding a new type value in the **flat** pattern. + +- **hierarchy** and **facets** (to a slightly lesser degree) are well-suited for strongly typed client programming languages, whereas **flat** is more familiar to developers of less strongly typed languages. + +- **facets** has the potential to model what is typically associated with multiple inheritance. + +- **facets** and **flat** lend to syntactically simpler filter query expression. **hierarchy** is more explicit but requires the less well known cast segments in the filter query. + +- **flat** might resemble a structure that developers are familiar with from on-premises products and their API (for example, recurrence in Microsoft Graph is modeled after Exchange Server's model). Even though the Microsoft Graph API can and should abstract from the implementation details, this can have benefits in documentation and adoption. + +- **hierarchy** can become hard to maintain if the base type is quite abstract and the **hierarchy** is relatively wide. Let's assume a situation where collections are modeled using the base type with many subtypes, but the actual elements of the collection are only ever one or two of the subtypes. When a new subtype gets introduced and the collection(s) quickly contain elements of this new subtype, client code has to react to these changes. It is important to check if this changes the semantics of the property (actual or assumed). + +- Even though not frequently used in Microsoft Graph, **hierarchy** can be refined by annotating the collections with OData derived type constraints; see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Validation.V1.md). This annotation restricts the values to certain sub-trees of an inheritance **hierarchy**. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return objects of a type that are semantically not suitable. + +> **Note** +> As can be seen in a few of the pros and cons, one of the important aspects discussed here is that the API design goes beyond the syntactical aspects of the API. Therefore, it is important to plan ahead how the API evolves, lay the foundation, and allow users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change.** The different modeling patterns differ in how they express syntax and semantics and how they allow the API to evolve without breaking compatibility. ### Behavior modeling From f0a1a7946d73d8823b2b7a68204790f0f5095cdf Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Fri, 24 Jun 2022 14:33:33 -0700 Subject: [PATCH 409/729] Added bookmark to API contract... heading. --- graph/GuidelinesGraph.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 26282ca0..3a8ca2f5 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -248,7 +248,7 @@ Following are a few pros and cons to decide which pattern to use: - Even though not frequently used in Microsoft Graph, **hierarchy** can be refined by annotating the collections with OData derived type constraints; see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Validation.V1.md). This annotation restricts the values to certain sub-trees of an inheritance **hierarchy**. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return objects of a type that are semantically not suitable. > **Note** -> As can be seen in a few of the pros and cons, one of the important aspects discussed here is that the API design goes beyond the syntactical aspects of the API. Therefore, it is important to plan ahead how the API evolves, lay the foundation, and allow users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change.** The different modeling patterns differ in how they express syntax and semantics and how they allow the API to evolve without breaking compatibility. +> As can be seen in a few of the pros and cons, one of the important aspects discussed here is that the API design goes beyond the syntactical aspects of the API. Therefore, it is important to plan ahead how the API evolves, lay the foundation, and allow users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change.** The different modeling patterns differ in how they express syntax and semantics and how they allow the API to evolve without breaking compatibility. For more information, see [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) later in this article. ### Behavior modeling @@ -326,6 +326,8 @@ The top-level error code must be aligned with HTTP response status codes accordi For a complete mapping of error codes to HTTP statuses, see [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). +
    + ## API contract and non-backward compatible changes The Microsoft Graph definition of breaking changes is based on the From 10d3d627d38c2d34bb742a5c9f161affd9335ee2 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Fri, 24 Jun 2022 14:37:32 -0700 Subject: [PATCH 410/729] Updated note formatting --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 3a8ca2f5..908a8d47 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -247,7 +247,7 @@ Following are a few pros and cons to decide which pattern to use: - Even though not frequently used in Microsoft Graph, **hierarchy** can be refined by annotating the collections with OData derived type constraints; see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Validation.V1.md). This annotation restricts the values to certain sub-trees of an inheritance **hierarchy**. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return objects of a type that are semantically not suitable. -> **Note** +> **Note:** > As can be seen in a few of the pros and cons, one of the important aspects discussed here is that the API design goes beyond the syntactical aspects of the API. Therefore, it is important to plan ahead how the API evolves, lay the foundation, and allow users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change.** The different modeling patterns differ in how they express syntax and semantics and how they allow the API to evolve without breaking compatibility. For more information, see [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) later in this article. ### Behavior modeling From 83b78760e791acfb6656a24471073b108d5b0895 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Fri, 24 Jun 2022 14:42:47 -0700 Subject: [PATCH 411/729] Minor grammar fix --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 908a8d47..c96247c3 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -331,7 +331,7 @@ For a complete mapping of error codes to HTTP statuses, see ## API contract and non-backward compatible changes The Microsoft Graph definition of breaking changes is based on the -[Microsoft REST API Guidelines definition of a breaking change](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#123-definition-of-a-breaking-change). In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a breaking change. +[Microsoft REST API Guidelines definition of a breaking change](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#123-definition-of-a-breaking-change). In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and is not considered a breaking change. **Non-breaking changes:** From 16f029f9bd71d349a15a5438cd8364c820f503c9 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin Date: Sun, 26 Jun 2022 11:43:09 -0700 Subject: [PATCH 412/729] Fix XML in collection subsets pattern --- graph/patterns/subsets.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph/patterns/subsets.md b/graph/patterns/subsets.md index 9931b49c..d9dd801c 100644 --- a/graph/patterns/subsets.md +++ b/graph/patterns/subsets.md @@ -25,12 +25,12 @@ The abstract base class should also hold an enum for all possible variants. The - + - + ``` **Derived types** @@ -61,11 +61,11 @@ These pattern type names should satisfy the following naming conventions: - + - + From cae645caf1f63be5d3de5dda6b3f9c145af91e4e Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:42:45 -0400 Subject: [PATCH 413/729] updated comparison criteria --- graph/GuidelinesGraph.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index c96247c3..437093a3 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -239,13 +239,9 @@ Following are a few pros and cons to decide which pattern to use: - **facets** has the potential to model what is typically associated with multiple inheritance. -- **facets** and **flat** lend to syntactically simpler filter query expression. **hierarchy** is more explicit but requires the less well known cast segments in the filter query. +- **facets** and **flat** lend to syntactically simpler filter query expression. **hierarchy** is more explicit but requires the cast segments in the filter query. -- **flat** might resemble a structure that developers are familiar with from on-premises products and their API (for example, recurrence in Microsoft Graph is modeled after Exchange Server's model). Even though the Microsoft Graph API can and should abstract from the implementation details, this can have benefits in documentation and adoption. - -- **hierarchy** can become hard to maintain if the base type is quite abstract and the **hierarchy** is relatively wide. Let's assume a situation where collections are modeled using the base type with many subtypes, but the actual elements of the collection are only ever one or two of the subtypes. When a new subtype gets introduced and the collection(s) quickly contain elements of this new subtype, client code has to react to these changes. It is important to check if this changes the semantics of the property (actual or assumed). - -- Even though not frequently used in Microsoft Graph, **hierarchy** can be refined by annotating the collections with OData derived type constraints; see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Validation.V1.md). This annotation restricts the values to certain sub-trees of an inheritance **hierarchy**. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return objects of a type that are semantically not suitable. +- **hierarchy** can be refined by annotating the collections with OData derived type constraints; see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Validation.V1.md). This annotation restricts the values to certain sub-trees of an inheritance **hierarchy**. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return objects of a type that are semantically not suitable. > **Note:** > As can be seen in a few of the pros and cons, one of the important aspects discussed here is that the API design goes beyond the syntactical aspects of the API. Therefore, it is important to plan ahead how the API evolves, lay the foundation, and allow users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change.** The different modeling patterns differ in how they express syntax and semantics and how they allow the API to evolve without breaking compatibility. For more information, see [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) later in this article. From 40abe334b27e867f598e210b1f8b1616cd3677cd Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 29 Jun 2022 10:23:01 -0400 Subject: [PATCH 414/729] added hybrid mode --- graph/patterns/longRunningOperations.md | 67 +++++++++++++++++++++++ graph/patterns/operations.md | 70 +++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 graph/patterns/operations.md diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index 81733b72..54c4da4c 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -241,3 +241,70 @@ HTTP/1.1 200 OK "resourceLocation": "https://graph.microsoft.com/v1.0/storage/archives/987" } ``` +### Trigger a long running action using the Stepwise Operation + +``` +POST https://graph.microsoft.com/v1.0/storage/copyArchive + +{ +"displayName": "Image Archive", +"destination": "Second-tier storage" +... +} +``` + +The API responds synchronously that the request has been accepted and includes +the Location header with an operation resource for further polling. + +``` +HTTP/1.1 202 Accepted + +Location: https://graph.microsoft.com/v1.0/storage/operations/123 + +``` +### Trigger a long running action using the Stepwise Operation in hybrid model + +The server responds synchronously to POST requests to collections that create a resource even if the resources aren't fully created when the response is generated. The response includes a representation of the incomplete resource that will eventually exist at the URL in the Content-Location header and the Location header with an operation resource for further polling. + +``` +POST https://graph.microsoft.com/v1.0/storage/databases/ + +{ +"displayName": "Retail DB", +} +``` + +The API responds synchronously that the database has been created and indicates +that the provisioning operation is not fully completed by including the +Content-Location header and status property in the response payload. + +``` +HTTP/1.1 202 Accepted +Content-Location: https://graph.microsoft.com/v1.0/storage/databases/db1 +Location: https://graph.microsoft.com/v1.0/storage/operations/123 +{ +"id": "db1", +"displayName": "Retail DB", +"status": "provisioning", +[ … other fields for "database" …] +} +``` +The client waits for a period of time then invokes another request to try to get the database status. + +``` + +GET https://graph.microsoft.com/v1.0/storage/operations/123 +``` +If resource creation is successful then the server responds with a "status:succeeded" and the resource +location. + +``` +HTTP/1.1 200 OK + +{ +"createdDateTime": "2015-06-19T12-01-03.45Z", +"lastActionDateTime": "2015-06-19T12-06-03.0024Z", +"status": "succeeded", +"resourceLocation": "https://graph.microsoft.com/v1.0/storage/databases/db1" +} +``` \ No newline at end of file diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md new file mode 100644 index 00000000..09db0c2a --- /dev/null +++ b/graph/patterns/operations.md @@ -0,0 +1,70 @@ +# Operations pattern + +Microsoft Graph API Design Pattern + +  + +### *Operations pattern provides an ability to model a change which impacts multiple resources and can't be effectively modeled using HTTP methods* + +
    + +## Problem +--- +Sometimes when modeling a complex business domain API designers need to model a business operation which effects multiple resources and needs to be performed as a single unit. Modeling the operation via HTTP methods on each individual resource may be either ineffective or doesn't reflect how it's proccessed by the backend service. In addition the operation may produce an observable side effects. + +* * + +## Solution +-------- + +To address these use cases an API designers may use operational resources such as functions or actions. +If the operation doesn't have any side effects and MUST return a single instance of a type or collection of instances then the designer SHOULD use OData function. +otherwise the designer can model operation as an action. + + +* * + + +## Issues and Considerations +--- + + - MS Graph does NOT support unbound actions or functions. + +Bound actions and functions are invoked on resources matching the type of the binding parameter. The binding parameter can be of any type, and it MAY be Nullable. For MS Graph, actions and functions must have the `isBound="true"` attribute. The first parameter is the binding parameter. + + - Both actions and functions support overloading, meaning a schema may contain multiple actions or functions with the same name.The overload rules as per the OData [standard](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionOverloads) apply when adding parameters to actions and functions. + + - As Graph only supports bound actions and functions, all must have at least one parameter where the first is the binding parameter. The MUSTS of parameters: + + - Each parameter must have a simple identifier name. + - The parameter name must be unique within the overload. + - The parameter must specify a type. + + - Graph supports the use of optional parameters. The optional parameter annotation can be used instead of creating function or action overloads when unnecessary. + + - API designer **MUST** use POST to call operations on resources. + | + - Addition of a new mandatory not nullable parameter to an existing action or function is a breaking change and is not allowed without proper versioning [](). + + +## When to Use this Pattern + +The operation pattern is well suited to use cases which cannot be modeled as a single HTTP method on a resource and require either multiple round trips to complete a single logical operation or produce one or multiple side effects. + +There are related patterns to consider such as + +[Long running operation](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Change Tracking](https://github.com/microsoft/api-guidelines/tree/graph/graph). + + +* * + +## Example +------- + +*Provide a short example from real life* + +* *  + + + + From 19c9472091506d3b28f2a1156d4045dce106432f Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 29 Jun 2022 11:31:58 -0400 Subject: [PATCH 415/729] added flat bag --- graph/patterns/flat-bag.md | 33 +++++++++++++++++++++++++++++++++ graph/patterns/subtypes.md | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 graph/patterns/flat-bag.md diff --git a/graph/patterns/flat-bag.md b/graph/patterns/flat-bag.md new file mode 100644 index 00000000..aded64e8 --- /dev/null +++ b/graph/patterns/flat-bag.md @@ -0,0 +1,33 @@ +# Flat Bag Pattern + +Microsoft Graph API Design Pattern + +### *A known pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with all the potential properties plus an additional property to distinguish the variants.* + + +## Problem + +API designer needs to model a small and limited number of variants of a common concept with a concise list of non-overlapping properties and consistent behavior across variant. The designer also wants to simplify query construction. + +## Solution + +The API designer creates one entity type with all the potential properties plus an additional property to distinguish the variants, often called `variantType`. + +## Issues and Considerations + +???? + + +## When to Use this Pattern + +The flat-bag pattern is useful when there is a small number of variants + +???? + +There are related patterns to consider such as +[Type Hierarchy](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat +bag of +properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). + +## Example +????? diff --git a/graph/patterns/subtypes.md b/graph/patterns/subtypes.md index 73dea0a2..50a5efb6 100644 --- a/graph/patterns/subtypes.md +++ b/graph/patterns/subtypes.md @@ -106,7 +106,7 @@ Response payload shortened for readability:     ] } ``` -API request for a subtype specific property requires type casting to the subtype, i.e. to retrieve jobTitle property, enabled for the user type, you need to cast from the directoryObject collection items to the microsoft.graph.group derived type. +API request for a subtype specific property requires type casting to the subtype, i.e. to retrieve jobTitle property, enabled for the user type, you need to cast from the directoryObject collection items to the microsoft.graph.user derived type. ``` GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members/microsoft.graph.user?$select=displayName,jobTitle From c7467f95c308b5fab2d291a9252907610ce79d6d Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 29 Jun 2022 14:10:25 -0400 Subject: [PATCH 416/729] Update longRunningOperations.md --- graph/patterns/longRunningOperations.md | 46 ------------------------- 1 file changed, 46 deletions(-) diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/longRunningOperations.md index 54c4da4c..0440237d 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/longRunningOperations.md @@ -261,50 +261,4 @@ HTTP/1.1 202 Accepted Location: https://graph.microsoft.com/v1.0/storage/operations/123 -``` -### Trigger a long running action using the Stepwise Operation in hybrid model - -The server responds synchronously to POST requests to collections that create a resource even if the resources aren't fully created when the response is generated. The response includes a representation of the incomplete resource that will eventually exist at the URL in the Content-Location header and the Location header with an operation resource for further polling. - -``` -POST https://graph.microsoft.com/v1.0/storage/databases/ - -{ -"displayName": "Retail DB", -} -``` - -The API responds synchronously that the database has been created and indicates -that the provisioning operation is not fully completed by including the -Content-Location header and status property in the response payload. - -``` -HTTP/1.1 202 Accepted -Content-Location: https://graph.microsoft.com/v1.0/storage/databases/db1 -Location: https://graph.microsoft.com/v1.0/storage/operations/123 -{ -"id": "db1", -"displayName": "Retail DB", -"status": "provisioning", -[ … other fields for "database" …] -} -``` -The client waits for a period of time then invokes another request to try to get the database status. - -``` - -GET https://graph.microsoft.com/v1.0/storage/operations/123 -``` -If resource creation is successful then the server responds with a "status:succeeded" and the resource -location. - -``` -HTTP/1.1 200 OK - -{ -"createdDateTime": "2015-06-19T12-01-03.45Z", -"lastActionDateTime": "2015-06-19T12-06-03.0024Z", -"status": "succeeded", -"resourceLocation": "https://graph.microsoft.com/v1.0/storage/databases/db1" -} ``` \ No newline at end of file From 9ae63f0ab70efd54f9cbdd22e01a112bf94c58cc Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 30 Jun 2022 12:00:03 -0400 Subject: [PATCH 417/729] updates from main --- SECURITY.md | 41 ++ azure/Guidelines.md | 45 +- graph/GuidelinesGraph.md | 521 +++++++++---------- graph/deprecation.md | 39 +- graph/patterns/PatternDescriptionTemplate.md | 40 +- graph/patterns/alternate-key.md | 153 +++--- graph/patterns/change-tracking.md | 54 +- graph/patterns/dictionary-client-guidance.md | 22 +- graph/patterns/dictionary.md | 75 ++- graph/patterns/evolvable-enums.md | 140 +++-- 10 files changed, 541 insertions(+), 589 deletions(-) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..869fdfe2 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a99869db..b89cc68c 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -3,6 +3,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2022-May-11 | Drop guidance on version discovery | | 2022-Mar-29 | Add guidelines about using durations | | 2022-Mar-25 | Update guideline for date values in headers to follow RFC 7231 | | 2022-Feb-01 | Updated error guidance | @@ -736,50 +737,6 @@ While removing a value from an enum is a breaking change, adding value to an enu > :ballot_box_with_check: **You SHOULD** use extensible enums unless you are positive that the symbol set will **NEVER** change over time. -#### Version Discovery - -Simpler clients may be hardcoded to a single version of a service. Since Azure services offer each version for a well-known period of time, a client that’s regularly maintained can be always operational without further complexity as long as during regular maintenance the client is moved forward to new versions in advance of older ones being retired. - -API version discovery is needed when either a given hosted service may expose a different API version to different clients (e.g. latest API version only available in certain regions or to certain tenants) or the service itself may exist in different instances (e.g. a service that may be run on Azure or hosted on-premises). -In both of those cases clients may get ahead of services in the API version they use. In might also be possible for a client version to ship ahead of its corresponding service update, leading to the same situation. Lastly, version discovery is useful for clients that want to warn operators that an API they depend on may expire soon. - -:white_check_mark: **DO** support API version discovery, including - -1. Support HTTP `OPTIONS` requests against all resources, including the root URL for a given tenant or the global root if no tenant identity is tracked or not a multi-tenant service - -2. Include the `api-supported-versions` header, containing a comma-separated list of versions conforming to the Azure versioning scheme. This list must include all group versions as well as all major-minor versions supported by the target resource. For cases where no specific version applies (e.g. sometimes the root resource), the list still must contain the group versions supported by the service. - -3. If a given service supports versions of the API that are known to be planned for deprecation in a year or less, it must include those versions (group and major.minor) in the `api-deprecated-versions` header. - -4. For services that do rolling updates where there is a point in time where some front-ends are ahead of others version-wise, all front-ends **MUST** report the previous version as the latest version until the rolling update covers all instances and only then switch over to reporting the new latest version. This ensures that clients will not detect a version and then get load-balanced into a front-end that does not support it yet. - -:ballot_box_with_check: **YOU SHOULD** support the following for version discovery: - -1. In addition to the functionality described here, services should support HTTP `OPTIONS` requests for other purposes such as further discovery, CORS, etc. - -2. Services should allow unauthenticated HTTP `OPTIONS` requests. When doing so, authors need to consider whether HTTP `OPTIONS` requests against non-existing resources result in 404s and whether that is leaking sensitive information. Certain scenarios, such as support for CORS pre-flight requests, require allowing unauthenticated HTTP `OPTIONS` requests. - -3. If using OData and addressing an expanded resource, the HTTP `OPTIONS` request should return the group versions that are supported across the expanded set. - -Example request to discover API versions (blob storage container list API): - -```text -OPTIONS /?comp=list HTTP/1.1 -host: accountname.blob.core.azure.net -``` - -Example response: - -```text -200 OK -api-supported-versions: 2011-08,2012-02,1.1,2.0 -api-deprecated-versions: 2009-04,1.0 -Content-Length: 0 -``` - -Clients that use version discovery are expected to cache version information. Since there’s a year of lead time after an API version shows in the `api-deprecated-versions` before it’s removed, checking once a week should provide sufficient lead time to client authors or operators. -In the rare case where a server rolls back a version that clients are already using, the service will reject requests because they are ahead of the latest version supported. Whenever a client sees a `version-too-new` error, it should re-execute its version discovery procedure. - ### Repeatability of requests The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 2ca83c75..437093a3 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -1,22 +1,22 @@ # Microsoft Graph REST API Guidelines -Table of Contents +Table of contents - [Microsoft Graph REST API Guidelines](#microsoft-graph-rest-api-guidelines) - [](#) - [History](#history) - [Introduction](#introduction) - [Legend](#legend) - - [Design Approach](#design-approach) + - [Design approach](#design-approach) - [Naming](#naming) - [Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) - - [Query Support](#query-support) - - [Resource Modeling Patterns](#resource-modeling-patterns) - - [Behavior Modeling](#behavior-modeling) - - [Error Handling](#error-handling) + - [Query support](#query-support) + - [Resource modeling patterns](#resource-modeling-patterns) + - [Behavior modeling](#behavior-modeling) + - [Error handling](#error-handling) - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) - - [Versioning and Deprecation](#versioning-and-deprecation) - - [Recommended API Patterns](#recommended-api-patterns) + - [Versioning and deprecation](#versioning-and-deprecation) + - [Recommended API design patterns](#recommended-api-design-patterns) - [References](#references) ## @@ -25,182 +25,164 @@ Table of Contents | Date | Notes | |-------------|-----------------------------| -| 2021-Sep-28 | Using summary and patterns style. | -| 2020-Oct-04 | Initial version in Wiki. | +| 2022-Jun-14 | Edit pass for formatting, links | +| 2021-Sep-28 | Using summary and patterns style | +| 2020-Oct-04 | Initial version in Wiki | ## Introduction -When building a digital ecosystem API usability becomes a business priority. Success of your ecosystem depends on APIs that are easy to discover, simple to use, fit for purpose, and consistent across your products. +When building a digital ecosystem API, usability becomes a business priority. The success of your ecosystem depends on APIs that are easy to discover, simple to use, fit for purpose, and consistent across your products. This document offers guidance that Microsoft Graph API producer teams MUST follow to -ensure that Microsoft Graph has a consistent and easy to use API surface. A new API design should meet the -following goals: +ensure that Microsoft Graph has a consistent and easy-to-use API surface. A new API design should meet the following goals: -\- Developer friendly via consistent naming, patterns, and web standards (HTTP, -REST, JSON) +- Be developer friendly by using consistent naming, patterns, and web standards (HTTP, REST, JSON). -\- Work well with SDKs in many programming languages. +- Work well with SDKs in many programming languages. -\- Sustainable & evolvable via clear API contracts. +- Be sustainable and evolvable by using clear API contracts. -The Microsoft Graph guidelines are an extension of the Microsoft REST API -guidelines. Readers are assumed also be reading and following the Microsoft REST API -guidelines except where this document outlines specific differences or exceptions to those guidelines. -Together these guidelines and a library of API patterns serve as the means by -which API teams discuss and come to consensus on API review requirements. +The Microsoft Graph REST API Guidelines are an extension of the +[Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). It is assumed that readers are following the Microsoft REST API Guidelines except where this document outlines specific differences or exceptions to those guidelines. Together, these guidelines and a library of API patterns serve as the means by which API teams discuss and come to consensus on API review requirements. Technology and software are constantly changing and evolving, and as such, this -is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface. Consequently, this document will more frequently change to add guidance in areas previously uncovered, or to clarify existing guidance. It will less frequently change the directional guidance it has already provided. [Open an -issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest -a change or propose a new idea. +is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface. Consequently, this document will frequently change to add guidance in areas previously uncovered or to clarify existing guidance. It will less frequently change the directional guidance it has already provided. To suggest a change or propose a new idea, +[open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose). ### Legend This document offers prescriptive guidance labeled as follows: -:heavy_check_mark: **MUST** satisfy this specification. +:heavy_check_mark: **MUST** satisfy this specification. -:no_entry: **MUST NOT** use this pattern. +:no_entry: **MUST NOT** use this pattern. -:ballot_box_with_check: **SHOULD** fulfill this specification. +:ballot_box_with_check: **SHOULD** fulfill this specification. -:warning: **SHOULD NOT** adopt this pattern. +:warning: **SHOULD NOT** adopt this pattern. -If not following these advices, you MUST disclose your reasons during the Graph API review. +If you do not follow this advice, you MUST disclose your reasons during the Microsoft Graph API review. -## Design Approach +## Design approach -The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote API-first design approach where you begin your product design by focusing on how information will be exchanged and represented and creating an interface contract for your API which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface from your implementation and is essential for agility, predictability, and reuse of your APIs. Established interface contract allows developers to use your API while internal teams are still working on implementation; API specifications enable designing of user experience and test cases in parallel. Starting with user-facing contracts also promotes a good understanding of system interactions, your modeling domain, and understanding of how the service will evolve. Microsoft Graph supports resource and query-based API styles which follow HTTP, REST, and JSON standards, where API contract is described using ODATA conventions and schema definition (see Documentation · OData - the Best Way to REST). -[Documentation · OData - the Best Way to REST](https://www.odata.org/documentation/)). +The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote an API-first design approach where you begin your product design by focusing on how information is exchanged and represented and by creating an interface contract for your API, which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface from your implementation and is essential for agility, predictability, and reuse of your APIs. -In general API design includes the following steps: +An established interface contract allows developers to use your API while internal teams are still working on implementation; API specifications enable designing user experience and test cases in parallel. Starting with user-facing contracts also promotes a good understanding of system interactions, your modeling domain, and an understanding of how the service will evolve. -- Outline the key current and future scenarios for API consumers +Microsoft Graph supports resource and query-based API styles that follow HTTP, REST, and JSON standards, where the API contract is described by using OData conventions and schema definitions. For more information, see [Documentation · OData Version 4.01](https://www.odata.org/documentation/). + +In general, API design includes the following steps: + +1. Outline the key current and future scenarios for API consumers. -- Define your domain model +1. Define your domain model. -- Derive and name your API resources +1. Derive and name your API resources. -- Describe relationships between resources +1. Describe relationships between resources. -- Determine required behavior +1. Determine required behavior. -- Determine user roles and application permissions +1. Determine user roles and application permissions. -- Specify errors +1. Specify errors. -When creating your API contract you will define resources based on the domain model supporting your service and identify interactions based on user scenarios. Good API design goes beyond modeling the current state of resources and it is important to plan ahead how API evolves. For this it is essential to understand and document your user scenarios as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. We recommend creating a simple resource diagram, like below, to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. -![Resource model example](ModelExample.png) +When creating your API contract, you define resources based on the domain model supporting your service and identify interactions based on user scenarios. Good API design goes beyond modeling the current state of resources. It is important to plan ahead how the API evolves; to do this, it is essential to understand and document your user scenarios as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. -After resources are defined it’s time to think about the behavior of your API which can be expressed via HTTP methods and operational resources such as functions and actions. As you think about API behavior you identify a happy path and various exceptions and deviations which will be expressed as errors and represented using HTTP codes and error messages. +We recommend creating a simple resource diagram like the following to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. + +![Resource model example](ModelExample.png) -At every step of your design you need to consider security, privacy and compliance as intrinsic components of your API implementation. +After you define your resources, it’s time to think about the behavior of your API, which can be expressed via HTTP methods and operational resources such as functions and actions. As you think about API behavior, you identify a happy path and various exceptions and deviations that will be expressed as errors and represented by using HTTP codes and error messages. +At every step of your design, you need to consider security, privacy, and compliance as intrinsic components of your API implementation. ### Naming -API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand for developers. Ease of understanding comes from familiarity and recognition therefore when thinking about naming you should favor consistency with other Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). - -Below is a short summary of the most often used conventions. - -| Requirements | Example | -|---------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| :no_entry: **MUST NOT** use redundant words in names. |- **Right:** /places/{id}/**displayName** and /phones/{id}/**number**
    - **Wrong** /places/{id}/*placeName* and /phones/{id}/**phoneNumber** | -| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong** teamsChat | -| :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong** msodsUrl or dlp | -| :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong** addresses | -| :heavy_check_mark: **MUST** use plural nouns for collections (for listing a type or collection properties). | - **Right:** addresses
    - **Wrong** address | -| :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a "postpositive"). | - **Right:** passersby or mothersInLaw
    - **Wrong** notaryPublics or motherInLaws | -| **casing** | | -| :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces | - **Right:** automaticRepliesStatus.
    - **Wrong** kebab-case or snake_case. | -| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong** iOLimit or totalIoAmount | -| :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong** webHTML | -| :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate or crosswalk
    - **Wrong** metaData, semiCircle or airPlane | -| :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw or postOffice
    - **Wrong** paperclip or fullmoon | -| **prefixes and suffixes** | | -| :heavy_check_mark: **MUST** suffix date and time properties with | - **Right:** dueDate — an Edm.Date
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Wrong** dueOn or startTime -
    - **Right:** instead both above are an Edm.DateTimeOffset | -| :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an int, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (NOTE use of Edm.Duration type is preferable)
    - **Wrong** passwordValidityPeriod — an Edm.Int32 | -| :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong** enabledBool | -| :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem, or userId on auditActor
    - **Wrong** webUrl on contact when its the companyWebUrl | -| :ballot_box_with_check: **SHOULD** prefix Boolean properties with is, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom — subjectively more natural than the examples below
    - **Wrong** isNewTimeProposalsAllowed or isInvitesFromAllowed — subjectively more awkward that the examples above | -| :no_entry: **MUST NOT** use 'collection', 'response', 'request ' suffixes . |- **Right:** addresses
    - **Wrong** addressCollection | +API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API naming guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#17-naming-guidelines). + +Following is a short summary of the most often used conventions. + +| Requirements | Example | +| ------------------------------------------------------------------------|-------------------------------| +| :no_entry: **MUST NOT** use redundant words in names. | - **Right:** /places/{id}/**displayName** or /phones/{id}/**number**
    - **Wrong:** /places/{id}/**placeName** or /phones/{id}/**phoneNumber** | +| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong:** teamsChat | +| :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong:** msodsUrl or dlp | +| :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong:** addresses | +| :heavy_check_mark: **MUST** use plural nouns for collections (for listing type or collection properties). | - **Right:** addresses
    - **Wrong:** address | +| :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a *postpositive*).| - **Right:** passersby or mothersInLaw
    - **Wrong:** notaryPublics or motherInLaws | +| **CASING** | | +| :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces. | - **Right:** automaticRepliesStatus
    - **Wrong:** kebab-case or snake_case | +| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong:** iOLimit or totalIoAmount | +| :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong:** webHTML | +| :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate, or crosswalk
    - **Wrong:** metaData, semiCircle, or airPlane | +| :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw, or postOffice
    - **Wrong:** paperclip or fullmoon | +| **PREFIXES AND SUFFIXES** | | +| :heavy_check_mark: **MUST** suffix date and time properties with Date, Time, or DateTime | - **Right:** dueDate — an Edm.Date
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Wrong:** dueOn or startTime
    - **Right:** Instead, both of the preceding are an Edm.DateTimeOffset | +| :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an `int`, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (use of Edm.Duration type is preferable)
    - **Wrong:** passwordValidityPeriod — an Edm.Int32 | +| :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong:** enabledBool | +| :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem or userId on auditActor
    - **Wrong:** webUrl on contact when it's the companyWebUrl | +| :ballot_box_with_check: **SHOULD** prefix Boolean properties with `is`, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong:** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom (subjectively more natural than the following examples)
    - **Wrong:** isNewTimeProposalsAllowed or isInvitesFromAllowed (subjectively more awkward that the preceding examples) | +| :no_entry: **MUST NOT** use collection, response, or request suffixes. | - **Right:** addresses
    - **Wrong:** addressCollection | ### Uniform Resource Locators (URLs) -A Uniform Resource Locator (URL) is how developers access the resources of your -API. +A Uniform Resource Locator (URL) is how developers access the resources of your API. -Navigation paths to Microsoft Graph resources are generally broken into multiple -segments: +Navigation paths to Microsoft Graph resources are generally broken into multiple segments, +`{scheme}://{host}/{version}/{category}/[{pathSegment}][?{query}]` where: -**{scheme}://{host}/{version}/{category}/[{pathSegment}][?{query}]** where +- `scheme` and `host` segments are always [`https://graph.microsoft.com`](https://graph.microsoft.com/v1.0/users). -- **scheme and host segments** are always - [https://graph.microsoft.com](https://graph.microsoft.com/v1.0/users); +- `version` can be v1.0 or beta. -- **version** can be V1.0 or beta; +- `category` is a logical grouping of APIs into top-level categories. -- **category** segment is a logical grouping of APIs into top-level - categories; +- `pathSegment` is one or many navigation segments that can address an entity, collection of entities, property, or operation available for an entity. -- **pathSegment** is one or many navigation segments which can address an entity, - collection of entities, property or operation available for an entity +- `query` string must follow the OData standard for query representations and is covered in the Query section of OData specifications. -- **query string** must follow the OData standard for query representations - and is covered in [Query](#query) section of OData specifications. +While HTTP defines no constraints on how different resources are related, it does encourage the use of URL path segment hierarchies to convey relationships. In Microsoft Graph, relationships between resources are supported by the OData concepts of singletons, entity sets, entities, complex types, and navigation properties. -While HTTP defines no constraints on how different resources are related -together, it does encourage the use of URL path segment hierarchies to convey -relationships. In Microsoft Graph relationships between resources are -supported by the OData concepts of singletons, entitySets, entities, complex -types and navigation properties. +In Microsoft Graph, a top-level API category might represent one of the following groupings: -In Microsoft Graph a top-level API category may represent one of the following -groupings: +- A core *user-centric concept* of Microsoft Graph: /users, /groups, or /me. -1. A core *user-centric concept* of the Graph, i.e. /users, /groups or /me. +- A Microsoft *product or service offering* covering multiple use cases: /teamwork, /directory. -2. A Microsoft *product or service offerings* covering multiple use cases, i.e. /teamwork, /directory. +- A *feature offering* covering a single use case and *shared* across multiple Microsoft products: /search, /notifications, /subscriptions. -3. A *feature offering* covering a single use case and *shared* across multiple - Microsoft products, i.e. /search, /notifications, /subscriptions. +- *Administrative configuration* functions for specific products: /admin/exchange. -4. *Administrative configuration* functions for specific products. i.e. /admin/exchange. +- Internal Microsoft requirements for publishing Privileged and Hidden APIs, routing, and load testing: /loadTestEntities. -5. Internal Microsoft requirements for publishing Privileged and Hidden APIs, - routing, and load testing, i.e./loadTestEntities. +Effectively, top-level categories define a perimeter for the API surface; thus, a new category creation requires additional rigor and governance approval. -Effectively top-level categories define a perimeter for the API surface thus a -new category creation requires additional rigor and governance approval. +### Query support -### Query Support +Microsoft Graph APIs should support basic query options in conformance with OData specifications and [Microsoft REST API Guidelines for error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses). -Microsoft Graph APIs should support basic query options in conformance with -OData specifications and [Microsoft REST API -Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). -|Requirements| +|Requirements | |----------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** support \$select on resource to enable properties projection | -| :ballot_box_with_check: **SHOULD** support \$filter with eq, ne operations on properties of entities for collections| -| :heavy_check_mark: **MUST** support [server-side pagination](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_ServerDrivenPaging) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL) for collections | -| :ballot_box_with_check: **SHOULD** support pagination $top, $skip and $count for collections | -| :ballot_box_with_check: **SHOULD** sorting with \$orderby both ascending and descending on properties of the entities | +| :heavy_check_mark: **MUST** support `$select on resource` to enable properties projection. | +| :ballot_box_with_check: **SHOULD** support `\$filter with eq`, `ne` operations on properties of entities for collections. | +| :heavy_check_mark: **MUST** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | +| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip` (or `$skipToken`). | +| :ballot_box_with_check: **SHOULD** support `$count` for collections. | +| :ballot_box_with_check: **SHOULD** support sorting with `$orderby` both ascending and descending on properties of the entities. | -The query options part of an OData URL can be quite long, potentially exceeding -the maximum length of URLs supported by components involved in transmitting or -processing the request. One way to avoid this is to use the POST verb instead of -GET with $query segment, and pass the query options part of the URL in the request body as described -in the chapter [OData Query -Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). +The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or processing the request. One way to avoid this is to use the POST verb instead of GET with the `$query` segment, and pass the query options part of the URL in the request body as described in the chapter +[OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). -Another way is to use JSON batch as described in the [Microsoft Graph Documentation] (https://docs.microsoft.com/en-us/graph/json-batching#bypassing-url-length-limitations-with-batching). +Another way to avoid this is to use JSON batch as described in the [Microsoft Graph batching documentation](https://docs.microsoft.com/graph/json-batching#bypassing-url-length-limitations-with-batching). -### Resource Modeling Patterns +### Resource modeling patterns -You can model structured resources for your APIs using OData Entity Type or Complex Type. The main difference between these types is that Entity type declares a key property to uniquely identify its objects and Complex Type does not. In Microsoft Graph this key property is called "id" for server-created key values. If there is a natural name for the key property then the workload can use that. -Since objects of complex types on Graph don’t have unique identifiers, they are not directly addressable via URIs and therefore you must not use Complex Type to model addressable resources, such as individually addressable items within a collection, for more information refer to [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. +You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type does not. In Microsoft Graph, this key property is called `id` for server-created key values. If there is a natural name for the key property, then the workload can use that. -```XML +Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must not use complex type to model addressable resources such as individually addressable items within a collection. For more information, see the [Microsoft REST API Guidelines collection URL patterns](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. + +```xml @@ -216,79 +198,78 @@ Since objects of complex types on Graph don’t have unique identifiers, they ar
    ``` -| Microsoft Graph rules for modeling complex resources | -|----------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use String type for id | -| :heavy_check_mark: **MUST** use a primary key composed of a single property | -| :heavy_check_mark: **MUST** use an object as the root of all JSON payloads | -| :heavy_check_mark: **MUST** use a root object with a value property to return a collection | -| :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous | -| :warning: **SHOULD NOT** add the property id to a complex type | -There are different approaches for designing an API resource model in situations -with multiple variants of a common concept. Type Hierarchy, Facets, and Flat bag -of properties are three most often used patterns in Microsoft Graph today: +| Microsoft Graph rules for modeling complex resources | +|---------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** use String type for ID. | +| :heavy_check_mark: **MUST** use a primary key composed of a single property. | +| :heavy_check_mark: **MUST** use an object as the root of all JSON payloads. | +| :heavy_check_mark: **MUST** use a root object with a value property to return a collection. | +| :heavy_check_mark: **MUST** include @odata.type annotations when the type is ambiguous. | +| :warning: **SHOULD NOT** add the property ID to a complex type. | + +There are different approaches for designing an API resource model in situations with multiple variants of a common concept. +The three most often used patterns in Microsoft Graph today are type hierarchy, facets, and flat bag of properties: + +- **[Type hierarchy](./patterns/subtypes.md)** is represented by one abstract base type with a few common properties and one subtype for each variant. + +- **[Facets](./patterns/facets.md)** are represented by a single entity type with common properties and one facet property (of complex type) per variant. The facet properties only have a value when the object represents that variant. -- Type hierarchy is represented by one abstract base type with a few common - properties and one sub-type for each variant [Modelling with Subtypes - Pattern](./Modelling%20with%20Subtypes%20Pattern.md) +- **Flat bag of properties** is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required or meaningful for the variant given by the type property. -- Facets are represented by a single entity type with common properties and - one facet property (of complex type) per variant. The facet properties only - have a value when the object represents that variant [Modelling with Facets - Pattern](./Modelling%20with%20Facets%20Pattern.md) +The following table shows a summary of the main qualities for each pattern and can help you select a pattern fit for your use case. -- Flat bag of properties is represented by one entity type with all the - potential properties plus an additional property to distinguish the - variants, often called type. The type property describes the variant and - also defines properties that are required/meaningful for the variant given - by the type property. [Modelling with Flat Bag - Pattern](./Modelling%20with%20Flat%20Bag%20Pattern.md) +| API qualities\patterns | Properties and behavior described in metadata | Supports combinations of properties and behaviors | Simple query construction | +|-------------------------|-----------------------------------------------|---------------------------------------------------|---------------------------| +| Type hierarchy | yes | no | no | +| Facets | partially | yes | yes | +| Flat | no | no | yes | -The following table shows summary of main qualities for each pattern and will -help to select a pattern fit for your use case. +#### Pros and cons -| API qualities\
    Patterns | Properties and behavior
    described in metadata | Supports combinations
    of properties and behaviors | Simple query construction | -|---------------------------------------------------|-------------------------------------|-----------------------------------|---------------------------| -| Type hierarchy | yes | no | no | -| Facets | partially | yes | yes | -|Flat bag | no | no | yes | +Following are a few pros and cons to decide which pattern to use: +- In **[hierarchy](./patterns/subtypes.md)**, the interdependencies of properties, that is, which properties are relevant for which variants, is fully captured in metadata, and client code can potentially leverage that to construct and/or validate requests. +- Introducing new cases in **hierarchy** is relatively isolated (which is why it is so familiar to OOP) and is considered backwards compatible (at least syntactically). +- Introducing new cases/variants in **[facets](./patterns/facets.md)** is straightforward. You need to be careful because it can introduce situations where previously only one of the facets was non-null and now all the old ones are null. This is not unlike adding new subtypes in the **hierarchy** pattern or adding a new type value in the **flat** pattern. -### Behavior Modeling +- **hierarchy** and **facets** (to a slightly lesser degree) are well-suited for strongly typed client programming languages, whereas **flat** is more familiar to developers of less strongly typed languages. -The HTTP operations dictate how your API behaves. The URL of an API, along with -its request/response bodies, establishes the overall contract that developers -have with your service. As an API provider, how you manage the overall request / -response pattern should be one of the first implementation decisions you make. -APIs SHOULD use resource-based designs with standard HTTP methods rather than operation resources if possible. - Operation resources are either functions or actions. According to [ODATA standards]( http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2) a function represents an operation which returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action may have side effects and may return a result represented as a single entity or collection of any type. +- **facets** has the potential to model what is typically associated with multiple inheritance. -| Microsoft Graph rules for modeling behavior | -|-----------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections. This approach requires the server to produce system generated identities. | -| :heavy_check_mark: **MUST** use PATCH to edit updatable resources | -| :heavy_check_mark: **MUST** use DELETE to delete deletable resources | -| :heavy_check_mark: **MUST** use GET for listing and reading resources. | -| :warning: **SHOULD NOT** use PUT for updating resources. | -| :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical operation. | +- **facets** and **flat** lend to syntactically simpler filter query expression. **hierarchy** is more explicit but requires the cast segments in the filter query. +- **hierarchy** can be refined by annotating the collections with OData derived type constraints; see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Validation.V1.md). This annotation restricts the values to certain sub-trees of an inheritance **hierarchy**. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return objects of a type that are semantically not suitable. -Operation resources must have a binding parameter matching the type of the bound resource. -In addition both actions and functions support overloading, meaning an API definition may contain multiple actions or functions with the same name. +> **Note:** +> As can be seen in a few of the pros and cons, one of the important aspects discussed here is that the API design goes beyond the syntactical aspects of the API. Therefore, it is important to plan ahead how the API evolves, lay the foundation, and allow users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change.** The different modeling patterns differ in how they express syntax and semantics and how they allow the API to evolve without breaking compatibility. For more information, see [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) later in this article. -For a complete list of standard HTTP operations you can refer to the [Microsoft -REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md#7102-error-condition-responses). +### Behavior modeling -### Error Handling +The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request/response pattern should be one of the first implementation decisions you make. -Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should -follow when returning error condition responses. You can improve API traceability -and consistency by using recommended Graph error model and the Graph Utilities library to provide a standard implementation for your service : +If possible, APIs SHOULD use resource-based designs with standard HTTP methods rather than operation resources. Operation resources are either functions or actions. According to [OData standards](http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2), a function represents an operation that returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action might have side effects and might return a result represented as a single entity or collection of any type. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +| Microsoft Graph rules for modeling behavior | +|------------------------------------------------------------------| +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.
    This approach requires the server to produce system generated identities. | +| :heavy_check_mark: **MUST** use PATCH to edit updatable resources. | +| :heavy_check_mark: **MUST** use DELETE to delete deletable resources. | +| :heavy_check_mark: **MUST** use GET for listing and reading resources. | +| :warning: **SHOULD NOT** use PUT for updating resources. | +| :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical operation. | + +Operation resources must have a binding parameter that matches the type of the bound resource. In addition, both actions and functions support overloading, meaning an API definition might contain multiple actions or functions with the same name. + +For a complete list of standard HTTP operations, see the [Microsoft REST API Guidelines error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses). + +### Error handling + +Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should follow when returning error condition responses. You can improve API traceability and consistency by using the recommended Microsoft Graph error model and the Microsoft Graph utilities library to provide a standard implementation for your service: + +```http { "error": { "code": "BadRequest", @@ -300,138 +281,112 @@ and consistency by using recommended Graph error model and the Graph Utilities l } } } -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The top-level error code must be aligned with HTTP response status codes according to [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). -The following examples demonstrate error modeling for common use cases: - -- **Simple error**: An API wants to report an error with top-level details - only. Then the error object contains the top-level error code, message and - target (optional). +``` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -{ - "error": { - "code": "BadRequest", - "message": "Cannot process the request because it is malformed or incorrect.", - "target": "Resource X (Optional)" - } -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The top-level error code must be aligned with HTTP response status codes according to [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). The following examples demonstrate error modeling for common use cases: -- **Detailed error**: An API needs to provide service-specific details of the - error via the innererror property of the error object. It is intended to allow - services to supply a specific error code to help differentiate errors that - share the same top-level error code but reported for different reasons. +- **Simple error**: An API wants to report an error with top-level details only. The error object contains the top-level error code, message and target (optional). -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -{ - "error": { - "code": "BadRequest", - "message": "Cannot process the request because it is malformed or incorrect.", - "innererror": { - "code": "requiredFieldOrParameterMissing", - + ```http + { + "error": { + "code": "BadRequest", + "message": "Cannot process the request because it is malformed or incorrect.", + "target": "Resource X (Optional)" + } } - } -} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ``` + +- **Detailed error**: An API needs to provide service-specific details of the error via the innererror property of the error object. It is intended to allow services to supply a specific error code to help differentiate errors that share the same top-level error code but are reported for different reasons. + + ```http + { + "error": { + "code": "BadRequest", + "message": "Cannot process the request because it is malformed or incorrect.", + "innererror": { + "code": "requiredFieldOrParameterMissing", + + } + } + } + ``` -| Microsoft Graph enforces the following error rules | -|-----------------------------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | -| :heavy_check_mark: **MUST** return a 403 Forbidden error when the application or signed-in user have insufficient permissions are present in the auth token. | -| :heavy_check_mark: **MUST** return a 429 Too Many Requests error when client exceeded throttling limits and 503 Service Unavailable when service overloaded but client is within throttling limits.| -| :ballot_box_with_check: **SHOULD** return a 404 Not Found error if a 403 would result in information disclosure. | +| Microsoft Graph enforces the following error rules | +|-------------------------------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** return an error property with a child code property in all error responses. | +| :heavy_check_mark: **MUST** return a 403 Forbidden error when the application or signed-in user has insufficient permissions present in the auth token. | +| :heavy_check_mark: **MUST** return a 429 Too Many Requests error when the client exceeded throttling limits, and a 503 Service Unavailable error when the service overloaded but the client is within throttling limits.| +| :ballot_box_with_check: **SHOULD** return a 404 Not Found error if a 403 error would result in information disclosure. | -For a complete mapping of error codes to HTTP statuses you can refer to the +For a complete mapping of error codes to HTTP statuses, see [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). + + ## API contract and non-backward compatible changes -Microsoft Graph definition of breaking changes is based on the [Microsoft REST -API -Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change). -In general, making all but additive changes to the API contract for existing elements is -considered breaking. Adding new elements is allowed and not considered a -breaking change. +The Microsoft Graph definition of breaking changes is based on the +[Microsoft REST API Guidelines definition of a breaking change](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#123-definition-of-a-breaking-change). In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and is not considered a breaking change. -\*\* Non-breaking changes:\*\* +**Non-breaking changes:** +- Adding properties that are nullable or have a default value +- Adding a member after the sentinel member to an evolvable enumeration +- Removing, renaming, or changing the type of annotation +- Changing the order of properties +- Changing the length or format of opaque strings, such as resource IDs +- Adding or removing an annotation OpenType="true" -- Addition of properties that are nullable or have a default value -- Addition of a member, after the sentinel member, to an evolvable enumeration -- Removal, rename, or change to the type of an annotation -- Changes to the order of properties -- Changes to the length or format of opaque strings, such as resource IDs -- Addition or removal of an annotation OpenType="true" +**Breaking changes:** -\*\* Breaking changes:\*\* +- Changing the URL or fundamental request/response associated with a resource +- Removing, renaming, or changing an incompatible type of a declared property +- Removing or renaming APIs or API parameters +- Adding a required request header +- Adding EnumType members for non-evolvable enumerations +- Adding Nullable="false" properties to existing types +- Adding a parameter not marked as [Nullable](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Nullable) to existing actions +- Adding a parameter not marked as [Optional](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md#OptionalParameter) to an existing function +- Changing top-level error codes +- Introducing server-side pagination to existing collections +- Making significant changes to the performance of APIs such as increased latency, rate limits, or concurrency -- Changes to the URL or fundamental request/response associated with a - resource -- Removal, rename, or change to an incompatible type of a declared property -- Removal or rename of APIs or API parameters -- Addition of a required request header -- Addition of a EnumType members for non-evolvable enumerations -- Addition of a Nullable="false" properties to existing types -- Addition of a parameter not marked as [Nullable](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Nullable) to existing actions -- Addition of a parameter not marked as [Optional](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md#OptionalParameter) to an existing function -- Changes to top-level error codes -- Introduction of server-side pagination to existing collections -- Significant changes to the performance of APIs such as increased latency, rate limits or concurrency. +### Versioning and deprecation +As the market and technology evolves, your APIs will require modifications. In this case, you must avoid breaking changes and add new resources and features incrementally. If that is not possible, then you must version elements of your APIs. Microsoft Graph allows versioning of elements, including entities and properties. Versioning involves adding a new, uniquely named version of the element and marking the old version as deprecated. +In some cases, there is a natural new name for the element. In other cases, where the original name is still the most descriptive, the suffix _v2 can be added to the original name to make it unique. The original element is then marked as deprecated by using annotations. -### Versioning and Deprecation -As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If that is not possible then you must version elements of your APIs. -Microsoft Graph allows versioning of elements including entities and properties. Versioning involves adding a new, uniquely named version of the element and marking the old version as deprecated. +Microsoft Graph provides two public endpoints to support the API lifecycle: +- [API sets on the v1.0 endpoint](https://graph.microsoft.com/v1.0) are in general availability (GA) status. +- [API sets on the beta endpoint](https://graph.microsoft.com/beta) are in beta or private preview status. -In some cases, there will be a natural new name for the element. In other cases, where the original name is still the most descriptive, the suffix _v2 can be added to the original name to make it unique. -The original element is then marked as deprecated using annotations. +Microsoft Graph APIs in the GA version guarantee API stability and consistency for its clients. If your API requires a breaking change in GA, then you MUST create new element versions and support deprecated elements for a minimum of 36 months or 24 months with demonstrated non-usage. -Microsoft Graph provides two public endpoints to support API lifecycle: -1. API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in general availability (GA) status. -2. API sets on the beta endpoint (https://graph.microsoft.com/beta) are in beta or private preview status. +On the beta endpoint, breaking changes and deprecation of APIs are allowed with consideration of dependencies and customer impact. It is a best practice to test new element versions on the beta endpoint at first and then promote API changes to the GA endpoint. -Microsoft Graph APIs in the GA version guarantee API stability and consistency for its clients. If your API requires a breaking change in GA, then you MUST create new element versions and support deprecated elements for a minimum of 36 months or 24 months with demonstrated non-usage. -On the beta endpoint breaking changes and deprecation of APIs are allowed with consideration of dependencies and customer impact. It is best practice to test new element versions on the beta endpoint at first then promote API changes to the GA endpoint. Detailed requirements for versioning and deprecation are described in the [Deprecation guidelines](./deprecation.md). +## Recommended API design patterns -## Recommended API Patterns - -The guidelines in previous sections are intentionally brief and provide a jump -start for Graph API developers. More detailed design guidance on REST APIs is -published at the [Microsoft REST API -Guidelines](https://github.com/microsoft/api-guidelines/) and Graph specific -patterns are outlined in the table below. - -Recommended API Design patterns: - -| Pattern | Description | Reference | -|-------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| -| Type Hierarchy | The ability to model is-a relationships using subtypes. | [Subtypes](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | -| Facets | The ability to model parent-child relationships using Facet pattern. | [Facets](./patterns/Modelling%20with%20Subtypes%20Pattern.md) | -| Dictionary | The ability for clients to provide an unknown quantity of data elements of the same type. | [Dictionary](./patterns/dictionary.md) | -| Evolvable Enums | The ability to extend enumerated types without breaking changes. | [Evolvable Enums](./patterns/evolvable-enums.md) | -| Change Tracking | The ability to keep API consumers in sync with changes without polling. | [Change Tracking](./patterns/change-tracking.md) | -| Namespace | The ability to organize resource definitions into a logical set. | [Namespace](./patterns/namespace.md) | - - | +The guidelines in previous sections are intentionally brief and provide a jump start for Microsoft Graph API developers. More detailed design guidance about REST APIs is published at the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). Microsoft Graph-specific patterns are outlined in the following table. +| Pattern | Description | +|--------------------------------------------------|----------------------------------------------------------------------------| +| [Alternate key](./patterns/alternate-key.md) | Uniquely identify and query resources using an alternate key. | +| [Change tracking](./patterns/change-tracking.md) | Keep API consumers in sync with changes without polling. | +| [Dictionary](./patterns/dictionary.md) | Clients can provide an unknown quantity of data elements of the same type. | +| [Evolvable enums](./patterns/evolvable-enums.md) | Extend enumerated types without breaking changes. | +| [Facets](./patterns/facets.md) | Model parent-child relationships. | +| [Modeling subsets](./patterns/subsets.md) | Model collection subsets for All, None, Included, or Excluded criteria. | +| [Namespace](./patterns/namespace.md) | Organize resource definitions into a logical set. | +| [Type hierarchy](./patterns/subtypes.md) | Model `is-a` relationships using subtypes. | ## References -- [Microsoft REST API - Guidelines](https://github.com/microsoft/api-guidelines/) - -- [OData Guidelines](http://www.odata.org/documentation/) - -- [RESTful web API - design](https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design) - -- [Microsoft Graph - Documentation](https://developer.microsoft.com/en-us/graph/docs/concepts/overview) - -- [Microsoft Graph Explorer](https://aka.ms/ge) +- [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/) +- [OData guidelines](http://www.odata.org/documentation/) +- [RESTful web API design](https://docs.microsoft.com/azure/architecture/best-practices/api-design) +- [Microsoft Graph documentation](https://docs.microsoft.com/graph/overview) +- [Graph Explorer](https://developer.microsoft.com/graph/graph-explorer) diff --git a/graph/deprecation.md b/graph/deprecation.md index b15efce7..2a932ba0 100644 --- a/graph/deprecation.md +++ b/graph/deprecation.md @@ -1,20 +1,19 @@ -### Deprecation Guidelines +# Deprecation guidelines -If your API requires the introduction of breaking changes you must add Revisions annotations to the API definition with the following terms: +If your API requires the introduction of breaking changes, you must add Revisions annotations to the API definition with the following terms: - - - Date: Date when the element was marked as deprecated. - - Version: Used to organize the ChangeLog. Use the format "YYYY-MM/Category" where "YYYY-MM" is the month the deprecation is announced, and "Category" is the category under which the change is described. - - Kind: Deprecated - - - Description: Human readable description of the change: Used in changelog, documentation etc. - - RemovalDate: Earliest date when the element may be removed. +- **Date:** Date when the element was marked as deprecated. +- **Version:** Used to organize the ChangeLog. Use the format "YYYY-MM/Category", where "YYYY-MM" is the month the deprecation is announced, and "Category" is the category under which the change is described. +- **Kind:** Deprecated +- **Description:** Human readable description of the change. Used in ChangeLog, documentation, etc. +- **RemovalDate:** Earliest date when the element can be removed. -The annotation can be applied to a type, an entity set, a singleton,a property, a -navigation property, a function or an action. If a type is marked as deprecated, it +The annotation can be applied to a type, an entity set, a singleton, a property, a +navigation property, a function, or an action. If a type is marked as deprecated, it is not necessary to mark the members of that type as deprecated, nor is it necessary to annotate any usages of that type. -**Example of property annotation:** +## Example of property annotation ```xml @@ -32,15 +31,13 @@ to annotate any usages of that type. ``` -When the request URL contains a reference to a deprecated model element, the gateway will add a [Deprecation -header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the -date the element was marked as deprecated) and a Sunset header (with the date of 2 -years beyond the Deprecation date) to the response. +When the request URL contains a reference to a deprecated model element, the gateway adds a [Deprecation header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the date the element was marked as deprecated) and a Sunset header (with the date of two years beyond the deprecation date) to the response. -**Deprecation header example:** +## Deprecation header example -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Deprecation: Wed, 30 Mar 2022 11:59:59 GMT -Sunset: Thursday, 30 June 2024 23:59:59 GMT -Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_state ; rel="deprecation"; type="text/html"; title="state" -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` + Deprecation: Wed, 30 Mar 2022 11:59:59 GMT + Sunset: Thursday, 30 June 2024 23:59:59 GMT + Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_state ; rel="deprecation"; type="text/html"; title="state" + +``` diff --git a/graph/patterns/PatternDescriptionTemplate.md b/graph/patterns/PatternDescriptionTemplate.md index 236aa2ff..26336e73 100644 --- a/graph/patterns/PatternDescriptionTemplate.md +++ b/graph/patterns/PatternDescriptionTemplate.md @@ -1,50 +1,30 @@ -# Pattern Name +# Pattern name Microsoft Graph API Design Pattern -  +*Provide a short description of the pattern.* -### *Provide a short description of the pattern.* - -
    ## Problem --------- -*Describe business context relevant for the pattern.* -*Provide a short description of the problem.* -* * +*Describe the business context relevant for the pattern.* + +*Provide a short description of the problem.* ## Solution --------- *Describe how to implement the solution to solve the problem.* -*Describe related patterns.* - -* * -## When to Use this Pattern ------------------------- +*Describe related patterns.* -*Describe when and why the solution is applicable and when it may not.* +## When to use this pattern -* * +*Describe when and why the solution is applicable and when it might not be.* -## Issues and Considerations -------------------------- +## Issues and considerations *Describe tradeoffs of the solution.* - -* * - ## Example -------- - -*Provide a short example from real life* - -* *  - - - +*Provide a short example from real life.* diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 80cbf03e..3754e702 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -1,41 +1,56 @@ -# Alternate Key Pattern +# Alternate key Microsoft Graph API Design Pattern -_The Alternate Key Pattern provides the ability to query for a single, specific resource identifiable through an alternative set of properties that is not its primary key_ +*The alternate key pattern provides the ability to query for a single, specific resource identifiable via one of an alternative set of properties that is not its primary key.* ## Problem ---- +The resources exposed in Microsoft Graph are identified through a primary key, which guarantees uniqueness inside the same resource collection. Often though, that same resource can also be uniquely identified by an alternative, more convenient property that provides a better developer experience. -The resources exposed in Graph are identified through a Primary Key - which guarantees uniqueness inside the same resource type. Often though, that same resource can also be uniquely identified by an alternative, more convenient property (or set of properties) that provides a better developer experience. +Take a look at the `user` resource: while the `id` is the typical way to get the resource details, the `mail` address is also a unique property that can be used to identify it. -Take a look at the `user` resource: while the `id` remains a perfectly valid way to get the resource details, the `mail` address is also an unique property that could be used to identify it. +The resource can be accessed using the `$filter` query parameter, such as -While it is still possible to use the `$filter` query parameter, such as - -`GET https://graph.microsoft.com/v1.0/users?$filter=mail eq 'bob@contoso.com'`, the returned result is wrapped in an array that needs to be unpacked. +```http +GET https://graph.microsoft.com/v1.0/users?$filter=mail eq 'bob@contoso.com' +``` +However, in this case, the returned result is wrapped in an array that needs to be unpacked. When the uniqueness of the property within the collection implies that only zero or one results can be returned from the call this array provides a suboptimal experience for callers. ## Solution ---- +Typically resources in Graph are accessed using a simple forward-slash delimited URL pattern (this pattern is sometimes referred to as key-as-segment). + +```http +https://graph.microsoft.com/v1.0/users/0 - Retrieves the employee with ID = 0. +``` + +However, resources can also be accessed using parentheses to delimit the key, like this: + +```http +https://graph.microsoft.com/v1.0/users(0) - Also retrieves the employee with ID = 0. +``` + +Resource addressing by using an alternative key can be achieved by using this same parentheses-style convention with one difference: alternate keys MUST specify the key property name to unambiguously determine the alternate key, like this: + +```http +https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com`. +``` -Resource addressing via an alternative key can be achieved using the same parentheses-style convention as for the canonical key, with one difference: single-part alternate keys MUST specify the key property name to unambiguously determine the alternate key. (Note: this is a hypothetical sample) +In the same way as requesting a resource via the canonical key, if a resource cannot be located that matches the alternate key, then a 404 must be returned. -https://graph.microsoft.com/v1.0/users(0) - Retrieves the employee with ID = 0 -https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com` +> **Note:** When requesting a resource via alternate keys, the simple slash-delimited URL style does not work. -## When to Use this Pattern +> **Note:** Do not use multi-part alternate keys. Feedback has been that customers find multi-part keys confusing. +> Either create a composite single-part surrogate key property or fall back to logical operations in a $filter clause. ---- +## When to use this pattern -This pattern works and makes sense when the alternate key is good enough to identify a single resource and provides an useful alternative to the client. +Use this pattern when your resource type has other keys than its canonical key which uniquely identify a single resource. ## Example ---- - -The same user identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with specified key property name, and the canonical short form without key property name +The same user is identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with a specified key property name, and the canonical short form without a key property name. Declare `mail` and `ssn` as alternate keys on an entity: @@ -75,13 +90,41 @@ Declare `mail` and `ssn` as alternate keys on an entity: 1. Get a specific resource through `$filter`: -```http -GET https://graph.microsoft.com/v1.0/users/?$filter=ssn eq '123-45-6789' -``` - -```json -{ - "value": [ + ```http + GET https://graph.microsoft.com/v1.0/users/?$filter=ssn eq '123-45-6789' + ``` + + ```json + { + "value": [ + { + "givenName": "Bob", + "jobTitle": "Retail Manager", + "mail": "bob@contoso.com", + "mobilePhone": "+1 425 555 0109", + "officeLocation": "18/2111", + "preferredLanguage": "en-US", + "ssn": "123-45-6789", + "surname": "Vance", + "userPrincipalName": "bob@contoso.com", + "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" + } + ] + } + ``` + +2. Get a specific resource either through its primary key or through the two alternate keys: + + ```http + GET https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 + GET https://graph.microsoft.com/v1.0/users(1a89ade6-9f59-4fea-a139-23f84e3aef66) + GET https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') + GET https://graph.microsoft.com/v1.0/users(mail='bob@contoso.com') + ``` + + All four yield the same response: + + ```json { "givenName": "Bob", "jobTitle": "Retail Manager", @@ -89,45 +132,37 @@ GET https://graph.microsoft.com/v1.0/users/?$filter=ssn eq '123-45-6789' "mobilePhone": "+1 425 555 0109", "officeLocation": "18/2111", "preferredLanguage": "en-US", + "ssn": "123-45-6789", "surname": "Vance", "userPrincipalName": "bob@contoso.com", "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" } - ] -} -``` - -2. Get a specific resource either through its primary key, or through the two alternate keys: - -```http -GET https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 -GET https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') -GET https://graph.microsoft.com/v1.0/users(mail='bob@contoso.com') -``` + ``` -**NOTE:** When requesting a resource through its primary key you might want to prefer to use key-as-segment (as shown above). Also, the key-as-segment does not work for alternate keys. - -All of the 3 will yield the sare response: - -```json -{ - "givenName": "Bob", - "jobTitle": "Retail Manager", - "mail": "bob@contoso.com", - "mobilePhone": "+1 425 555 0109", - "officeLocation": "18/2111", - "preferredLanguage": "en-US", - "ssn": "123-45-6789", - "surname": "Vance", - "userPrincipalName": "bob@contoso.com", - "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" -} -``` +3. Request a resource for an unsupported alternate key property: -3. Requesting a resource for an unsupported alternate key property + ```http + GET https://graph.microsoft.com/v1.0/users(name='Bob') + + 400 Bad Request + { + "error" : { + "code" : "400", + "message": "'name' is not a valid alternate key for the resource type 'user'." + } + } + ``` -```http -GET https://graph.microsoft.com/v1.0/users(name='Bob') +4. Request a resource where the alternate key property does not exist on any resource in the colleciton: -400 Bad Request -``` + ```http + GET https://graph.microsoft.com/v1.0/users(email='unknown@contoso.com') + + 404 Not Found + { + "error" : { + "code" : "404", + "message": "No user with the the specified 'email' could be found." + } + } + ``` diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 0c33f5fb..2ac39056 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -1,59 +1,55 @@ -# Change Tracking +# Change tracking Microsoft Graph API Design Pattern *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.* ## Problem ---------- -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. +API consumers require an efficient way to keep data in sync with Microsoft Graph. The API design should be optimized to avoid polling because it is costly for consumers and producers alike and wouldn't guarantee data integrity. ## Solution --------- 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. -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. +This new endpoint can be used to sync API consumers. This is achieved through returning a delta link with a watermark. After the API consumer refreshes 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. -> 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. +> **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. -## Issues and Considerations -------------------------- +## When to use this pattern -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...) +Before using the change tracking pattern in your API definition, make sure that your scenario fits the following criteria: -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. +- API consumers want to sync the data. +- API consumers don't want to be immediately notified of changes (see the change notifications pattern for this scenario). +- API consumers are not looking for a "one-time" export or back-up mechanism. -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. -- The reason MUST be set to `changed` if the entity can be restored. `"@removed": {"reason": "changed"}`. -- The reason MUST be set to `deleted` if the entity cannot be restored. `"@removed": {"reason": "deleted"}`. +### Alternatives -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): +- Change notifications pattern (TODO add link when described) +- Backup pattern (TODO) -- When a user is added to the group `"members@delta": [{ "@odata.type": "#microsoft.graph.user", "id of the added user"}]` -- 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"}]` +## Issues and considerations -> 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. +The implementer MUST implement a watermark storage system in case of active watermarks. Passive watermarks are watermarks that can be retrieved from the context (for example, timestamp). Active watermarks represent information required to track the sync state, which cannot be retrieved from the context (such as a cursor from data store, partition affinity marker, partition ID, or generated unique sync identifier). -## When to Use this Pattern ------------------------- +The implementer MUST implement soft deletion for entities in the backend storage system. The soft deletion provides useful information to the client to appropriately reflect deletions. -Before using the change tracking pattern in your API definition, make sure your scenario fits the following criteria: +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. -- API consumers want to sync the data. -- API consumers don't want to be immediately notified of changes. (see change notifications pattern for this scenario). -- API consumers are not looking for a "one-time" export or back-up mechanism. +- The reason MUST be set to `changed` if the entity can be restored: `"@removed": {"reason": "changed"}` +- The reason MUST be set to `deleted` if the entity cannot be restored: `"@removed": {"reason": "deleted"}` -### Alternatives +When a link to an entity is deleted, when the linked entity is deleted, or when a link to an entity is added, the implementer MUST return a `property@delta` annotation. For example, considering the entity Group has a navigation property named members of type Collection(user): -- Change notifications pattern (TODO add link when described) -- Backup pattern (TODO) +- When a user is added to the group `"members@delta": [{ "@odata.type": "#microsoft.graph.user", "id of the added user"}]` +- When a user is removed from the group, or the target user is deleted from `"members@delta": [{"@removed": {"reason": "deleted"}, "id of the deleted or removed user"}]` + +> **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 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. However, expanding across workloads is not currently supported. ## Examples -------- -### Getting changes for the users entity set +### Get changes for the users entity set ```HTTP GET https://graph.microsoft.com/v1.0/users/delta @@ -94,7 +90,7 @@ GET https://graph.microsoft.com/v1.0/users/delta } ``` -> 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. +> **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 that the application can request right away to get the next page. ### CSDL example diff --git a/graph/patterns/dictionary-client-guidance.md b/graph/patterns/dictionary-client-guidance.md index 648f1385..14a0a219 100644 --- a/graph/patterns/dictionary-client-guidance.md +++ b/graph/patterns/dictionary-client-guidance.md @@ -1,16 +1,16 @@ # Dictionary types -Note: this document is to be moved into a central client guidance document in the future. +> **Note:** This document will be moved into a central client guidance document in the future. -The client guidance is a collection of additional information provided to SDK implementers and client applications. This information is meant to help understand how various guidelines and concept translate in their world and clarify a few unknowns. You should always read the corresponding guideline first to get a context understanding. +*The client guidance is a collection of additional information provided to SDK implementers and client applications. This information is meant to help understand how various guidelines and concepts translate in their world and clarify a few unknowns. Always read the corresponding guideline first to get a contextual understanding.* -[Read the guideline](./index.md). +For more information, see the [Dictionary](./dictionary.md) pattern. ## OpenAPI example -The following json-schema/OpenAPI example defines a dictionary of which values will by of type **RoleSettings**. +The following json-schema/OpenAPI example defines a dictionary of which values are of type **RoleSettings**. -In **components** in **schemas**: +In **components** in **schemas**: ```json { @@ -38,13 +38,13 @@ In **components** in **schemas**: } ``` -## SDK Support +## SDK support -SDKs need to provide support for dictionary types so SDK consumers get a delightful development experience. Examples are provided below for different languages. Other aspects need to be taken into considerations: +SDKs need to provide support for dictionary types so that SDK consumers get a delightful development experience. Examples are provided for different languages. Other aspects need to be taken into consideration: -- Dictionaries support OData annotations (values prefixed with **@OData**), such annotations should not be inserted directly in the dictionary but rather in the additional properties manager. -- Dictionary types can inherit another dictionary type, this inheritance must be respected. -- Dictionary values can be of union types, if the target language doesn't support union types, a wrapper type should be generated as backward compatible solution with properties for each type of the union. +- Dictionaries support OData annotations (values prefixed with **@OData**); such annotations should not be inserted directly in the dictionary but rather in the additional properties manager. +- Dictionary types can inherit another dictionary type; this inheritance must be respected. +- Dictionary values can be of union types; if the target language doesn't support union types, a wrapper type should be generated as a backward compatible solution with properties for each type of the union. ### Dotnet @@ -74,6 +74,6 @@ or ## Request builder generation annotation -By default SDKs are not required to contain a set of request builders to run CRUD requests on entries in the dictionary. The dictionary will be updated as a whole by consumers by sending requests to the parent entity. +By default, SDKs are not required to contain a set of request builders to run CRUD requests on entries in the dictionary. The dictionary is updated as a whole by consumers by sending requests to the parent entity. If a **SupportedHttpMethod** annotation is specified for the dictionary type, request builders should be generated to allow consumers to automatically update the entries. diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 0a797d5d..77a2ef2f 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -2,53 +2,46 @@ Microsoft Graph API Design Pattern -*The Dictionary type provides the ability to create a set of primitives or objects, of the same type, where the API consumer can define a name for each value in the set.* +*The dictionary type provides the ability to create a set of primitives or objects of the same type where the API consumer can define a name for each value in the set.* ## Problem --------- -The API design requires a resource to include an unknown quantity of data elements of the same type that must be named using values provided by the API consumer. +The API design requires a resource to include an unknown quantity of data elements of the same type that must be named by using values provided by the API consumer. ## Solution --------- -API designers use a JSON object to represent a dictionary in a `application/json`response payload. When describing the model in CSDL, a new complex type can be created that derives from `Org.OData.Core.V1.Dictionary` and then use the `Org.OData.Validation.V1.OpenPropertyTypeConstraint`to constrain the type that can be used for the values in the dictionary. +API designers use a JSON object to represent a dictionary in an `application/json` response payload. When describing the model in CSDL, a new complex type can be created that derives from `Org.OData.Core.V1.Dictionary` and then uses the `Org.OData.Validation.V1.OpenPropertyTypeConstraint` to constrain the type that can be used for the values in the dictionary. -Dictionary entries can be added via `POST`, updated via `PATCH`, and they can be removed by setting the entry value to `null`. Multiple entries can be updated at once by using `PATCH` on the dictionary property. +Dictionary entries can be added via `POST`, updated via `PATCH`, and removed by setting the entry value to `null`. Multiple entries can be updated at the same time by using `PATCH` on the dictionary property. -## Issues and Considerations -------------------------- +## When to use this pattern -Dictionaries, sometimes called maps, are a collection of name-value pairs. They allow dynamic data sets to be accessed in a systematic manner and are a good compromise between a strictly defined ahead of time structure with all its named properties and between a loosely defined dynamic object (i.e. OData OpenTypes). +Before using a dictionary type in your API definition, make sure that your scenario fits the following criteria: -As dictionary entries are removed via setting the value to null, this means that dictionaries can only support values that are non-nullable. +- The data values MUST be related to one another semantically as a collection. +- The value types MUST be a primitive type or a **ComplexType**. Mixed primitive types are not allowed. +- The client MUST define the keys of this type, as opposed to the service defining them in advance. - OpenQuestions: - - Can/should PUT be supported on the dictionary property and/or the entry value - - What does OData say about being able to POST to a structured property? Will OData Web API allow that? - - Must an implementer support PATCH at both the dictionary level and the entry level? +### Alternatives -More information: +- [Open extensions](https://docs.microsoft.com/graph/extensibility-open-users) when you want to provide clients the ability to extend Microsoft Graph. +- [Complex types](https://docs.microsoft.com/odata/webapi/complextypewithnavigationproperty) when the set of data values are known. -- [OData reference](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md#dictionary) +## Issues and considerations -  -## When to Use this Pattern ------------------------- +Dictionaries, sometimes called maps, are a collection of name-value pairs. They allow dynamic data sets to be accessed in a systematic manner and are a good compromise between a strictly defined-ahead-of-time structure with all its named properties and a loosely defined dynamic object (such as OData OpenTypes). -Before using a dictionary type in your API definition make sure your scenario fits the following criteria: +Because dictionary entries are removed by setting the value to `null`, dictionaries can only support values that are non-nullable. -- The data values MUST be related to one another semantically as a collection. -- The value types MUST be a primitive type or is a **ComplexType**. Mixed primitive types are not allowed. -- The client MUST define the keys of this type. As opposed to the service defining them in advance. +Open questions: -### Alternatives +- Can/should PUT be supported on the dictionary property and/or the entry value? +- What does OData say about being able to POST to a structured property? Will OData Web API allow that? +- Must an implementer support PATCH at both the dictionary level and the entry level? -- [Open extensions](https://docs.microsoft.com/en-us/graph/extensibility-open-users) when you want to provide clients the ability to extend Microsoft Graph. -- [Complex types](https://docs.microsoft.com/en-us/odata/webapi/complextypewithnavigationproperty) when the set of data values are known. +For more information, see the [OData reference](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md#dictionary). ## Examples -------- ### JSON payload example @@ -70,9 +63,9 @@ The following example illustrates the resulting JSON for a property of dictionar ### HTTP calls examples -In this set of examples we're modeling a **roles** property of dictionary type on the user entity which is exposed by the users entity set. +In this set of examples, we model a **roles** property of dictionary type on the user entity, which is exposed by the users entity set. -#### Getting an entry from the dictionary +#### Get an entry from the dictionary ```HTTP GET https://graph.microsoft.com/v1.0/users/10/roles/author @@ -86,7 +79,7 @@ Response: } ``` -#### Getting the dictionary +#### Get the dictionary ```HTTP GET https://graph.microsoft.com/v1.0/users/10/roles @@ -108,7 +101,7 @@ Response: } ``` -#### Getting the entity with the dictionary +#### Get the entity with the dictionary ```HTTP GET https://graph.microsoft.com/v1.0/users/10 @@ -134,7 +127,7 @@ Response: } ``` -#### Creating an entry in the dictionary +#### Create an entry in the dictionary ```HTTP POST https://graph.microsoft.com/v1.0/users/10/roles/author @@ -144,7 +137,7 @@ POST https://graph.microsoft.com/v1.0/users/10/roles/author } ``` -#### Updating the dictionary +#### Update the dictionary ```HTTP PATCH https://graph.microsoft.com/v1.0/users/10/roles @@ -163,11 +156,13 @@ PATCH https://graph.microsoft.com/v1.0/users/10/roles } ``` -> Note: setting one of the keys to **null** deletes it from the dictionary. -> Note: the domain values for the existing author and maintainer entries will get updated. -> Note: the reviewer entry will be inserted in the dictionary. +> **Notes:** +> +> - Setting one of the keys to **null** deletes it from the dictionary. +> - The domain values for the existing author and maintainer entries are updated. +> - The reviewer entry is inserted in the dictionary. -#### Updating an entry in the dictionary +#### Update an entry in the dictionary ```HTTP PATCH https://graph.microsoft.com/v1.0/users/10/roles/author @@ -177,7 +172,7 @@ PATCH https://graph.microsoft.com/v1.0/users/10/roles/author } ``` -#### Deleting an entry from the dictionary +#### Delete an entry from the dictionary ```HTTP DELETE https://graph.microsoft.com/v1.0/users/10/roles/author @@ -212,6 +207,6 @@ The following example defines a complex type **roleSettings** as well as a dicti ``` -### Additional information +## See also -[SDK implementation guidance](./client-guidance.md). +- [SDK implementation guidance](./dictionary-client-guidance.md) diff --git a/graph/patterns/evolvable-enums.md b/graph/patterns/evolvable-enums.md index 074aa42e..11d12918 100644 --- a/graph/patterns/evolvable-enums.md +++ b/graph/patterns/evolvable-enums.md @@ -1,4 +1,4 @@ -# Evolvable Enums +# Evolvable enums Microsoft Graph API Design Pattern @@ -6,83 +6,79 @@ Microsoft Graph API Design Pattern ## Problem ---- -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. +Frequently API producers want to add new members to an enum type after it is initially published. Some serialization libraries might fail when they encounter members in an enum type that were added after the serialization model was generated. In this documentation, we refer to any added enum members as unknown. ## Solution ---- -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 ```Prefer: 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-members``` HTTP header in the response. +The solution is to add a 'sentinel' member named `unknownFutureValue` at the end of the currently known enum members. The API producer then replaces any member that is numerically after `unknownFutureValue` with `unknownFutureValue`. -## When to Use this Pattern +If an API consumer can handle unknown enum values, the consumer can opt into receiving the unknown enum members by specifying the `Prefer: include-unknown-enum-members` HTTP header in their requests. The API producer then indicates that this preference has been applied by returning the `Preference-Applied: include-unknown-enum-members` HTTP header in the response. ---- +## When to use this pattern -- 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 require extending the enum. +It is a best practice to include an `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 that they have included all possible members in an enum, we still strongly recommend that you include an `unknownFutureValue` member to allow for unforeseen future circumstances that may require extending the enum. -- 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. +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. -## Issues and Considerations +## Issues and considerations ---- +Consider the following: -- 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. +- 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. -- Changing the value (i.e. position) of the ```unknownFutureValue``` sentinel member is considered a breaking change, and must follow the [deprecation](../deprecation.md) process. +- Changing the value (that is, position) of the `unknownFutureValue` sentinel member is considered a breaking change and must follow the [deprecation](../deprecation.md) process. -- 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. +- 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. -- 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. +- There is no ability for a client to indicate that it can handle a subset of unknown enum members. Instead, they can only specify that either they cannot handle any unknown enum members or they can handle any unknown enum members. -- The ```Prefer: 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. +- The `Prefer: 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. -- 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. +- 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 that 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 the next power of 2 value. -- 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. +- 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. -- If the value of a property containing a flag enum contains multiple unknown values they should all be replaced with a single ```unknownFutureValue``` value (i.e. there should not have multiple ```unknownFutureValue``` values returned). +- If the value of a property containing a flag enum contains multiple unknown values, they should all be replaced with a single `unknownFutureValue` value (that is, there should not be multiple `unknownFutureValue` values returned). -- 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. +- If an API consumer specifies `unknownFutureValue` for the value of a property in a `POST`/`PUT` request or as a parameter of an action or function, the API producer must reject the request with a `400 Bad Request` HTTP status. -- 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. +- 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 (that is, the existing value should not be changed). In the case where the API producer treats `PATCH` as an upsert, the call MUST be rejected with a `400 Bad Request` HTTP status. -- If an API consumer specifies an enum member greater than ```unknownFutureValue``` in any request without specifying the ```Prefer: include-unknown-enum-members``` header the API producer must reject the request with a ```400 Bad Request``` HTTP status. +- If an API consumer specifies an enum member greater than `unknownFutureValue` in any request without specifying the `Prefer: include-unknown-enum-members` header, the API producer must reject the request with a `400 Bad Request` HTTP status. -- For details of how the ```unknownFutureValue``` value is handled as part of a ```$filter``` clause please consult the following examples: +- For details about how the `unknownFutureValue` value is handled as part of a `$filter` clause, consult the following examples: -### CSDL + - **CSDL** + + ```xml + + + + + + + + + + + ``` + + - **Filter behavior** + + | `$filter` clause | `Prefer: include-unknown-enum-members` Absent | `Prefer: include-unknown-enum-members` Present | + |---|---|---| + | `enumProperty eq unknownFutureValue`| Return entities where enumProperty has any value greater than `unknownFutureValue` replacing actual value with `unknownFutureValue`| Return nothing | + | `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` | + | `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`| + | `enumProperty eq newValue` | `400 Bad Request` | Return entities where enumProperty has the value `newValue` | + | `enumProperty gt newValue` | `400 Bad Request` | Return entities where enumProperty has a value greater than `newValue` | + | `enumProperty lt newValue` | `400 Bad Request` | Return entities where enumProperty has a value less than `newValue` | -```xml - - - - - - - - - - -``` - -### Filter Behavior - -| ```$filter``` clause | ```Prefer: include-unknown-enum-members``` Absent | ```Prefer: include-unknown-enum-members``` Present | -|---|---|---| -| ```enumProperty eq unknownFutureValue```| Return entities where enumProperty has any value greater than ```unknownFutureValue``` replacing actual value with ```unknownFutureValue```| Return nothing | -| ```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``` | -| ```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```| -| ```enumProperty eq newValue``` | ```400 Bad Request``` | Return entities where enumProperty has the value ```newValue``` | -| ```enumProperty gt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value greater than ```newValue``` | -| ```enumProperty lt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value less than ```newValue``` | - -- 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 ```Prefer: include-unknown-enum-members``` header is absent. +- 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 `Prefer: include-unknown-enum-members` header is absent. ## Examples ---- - -For the following examples we will consider the ```managedDevice``` entity which refers to the ```managedDeviceArchitecture``` enum type. +For the following examples, we consider the `managedDevice` entity, which refers to the `managedDeviceArchitecture` enum type. ```xml @@ -92,7 +88,7 @@ For the following examples we will consider the ```managedDevice``` entity which ``` -When the ```managedDeviceArchitecture``` enum was initially published to Graph it was defined as below: +When the `managedDeviceArchitecture` enum was initially published to Microsoft Graph, it was defined as follows: ```xml @@ -106,7 +102,7 @@ When the ```managedDeviceArchitecture``` enum was initially published to Graph i ``` -The enum was later extended to add a new value of ```quantum``` leading to the below CSDL +The enum was later extended to add a new value of `quantum`, leading to the following CSDL: ```xml @@ -120,7 +116,7 @@ The enum was later extended to add a new value of ```quantum``` leading to the b ``` -### Default Behavior +### Default behavior ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture @@ -148,9 +144,9 @@ GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=dis } ``` -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``` +In this case, the value of the `processorArchitecture` property is `quantum`. However, because the client did not request the `include-unknown-enum-members` header, the value was replaced with `unknownFutureValue`. -### Include opt-in Header +### Include opt-in header ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture @@ -179,7 +175,7 @@ Preference-Applied: include-unknown-enum-members } ``` -### Default Sort Behavior +### Default sort behavior ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$orderBy=processorArchitecture @@ -204,7 +200,7 @@ GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=dis } ``` -### Sort Behavior with opt-in Header +### Sort behavior with opt-in header ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture @@ -233,7 +229,7 @@ Preference-Applied: include-unknown-enum-members } ``` -### Default Filter Behavior +### Default filter behavior ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$filter=processorArchitecture gt x64 @@ -254,7 +250,7 @@ GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=dis } ``` -### Filter Behavior with opt-in Header +### Filter behavior with opt-in header ```http GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$filter=processorArchitecture gt x64 @@ -279,7 +275,7 @@ Preference-Applied: include-unknown-enum-members } ``` -### Patch Example +### Patch example ```http PATCH https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/1 @@ -313,9 +309,9 @@ Preference-Applied: include-unknown-enum-members } ``` -## Flag Enum Examples +## Flag enum examples -For the following examples we will consider the ```windowsUniversalAppX``` entity which refers to the ```windowsArchitecture``` flag enum type. +For the following examples, we consider the `windowsUniversalAppX` entity, which refers to the `windowsArchitecture` flag enum type. ```xml @@ -325,7 +321,7 @@ For the following examples we will consider the ```windowsUniversalAppX``` entit ``` -When the ```windowsArchitecture``` enum was initially published to Graph it was defined as below: +When the `windowsArchitecture` enum was initially published to Microsoft Graph, it was defined as follows: ```xml @@ -339,7 +335,7 @@ When the ```windowsArchitecture``` enum was initially published to Graph it was ``` -The enum was later extended to add a new value of ```quantum``` leading to the below CSDL +The enum was later extended to add a new value of `quantum`, leading to the following CSDL: ```xml @@ -353,7 +349,7 @@ The enum was later extended to add a new value of ```quantum``` leading to the b ``` -### Flag Enum Default Behavior +### Flag enum default behavior ```http GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures @@ -381,9 +377,9 @@ GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=disp } ``` -In this case the value of the ```applicableArchitectures``` property includes ```quantum``` however since the client did not request the ```include-unknown-enum-members``` header the value was replaced with ```unknownFutureValue``` +In this case, the value of the `applicableArchitectures` property includes `quantum`. However, because the client did not request the `include-unknown-enum-members` header, the value was replaced with `unknownFutureValue`. -### Flag Enum Include opt-in Header +### Flag enum include opt-in header ```http GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures @@ -415,7 +411,7 @@ Preference-Applied: include-unknown-enum-members } ``` -### Flag Enum Default Filter Behavior +### Flag enum default filter behavior ```http GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures&$filter=applicableArchitectures has unknownFutureValue @@ -438,7 +434,7 @@ GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=disp } ``` -### Flag Enum Include opt-in Header Filter Behavior +### Flag enum include opt-in header filter behavior ```http GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures&$filter=applicableArchitectures has unknownFutureValue @@ -454,7 +450,7 @@ Preference-Applied: include-unknown-enum-members } ``` -### Flag Enum Patch Example +### Flag enum patch example ```http PATCH https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps/1 From 3c3458b0b8aaf3f2280d5ddb70618fc8134b3911 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 30 Jun 2022 12:13:04 -0400 Subject: [PATCH 418/729] flat-bag updates --- graph/patterns/facets.md | 2 +- graph/patterns/flat-bag.md | 50 ++++++++++++++++++++++++++------------ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/graph/patterns/facets.md b/graph/patterns/facets.md index 90122c9d..e4238239 100644 --- a/graph/patterns/facets.md +++ b/graph/patterns/facets.md @@ -65,7 +65,7 @@ The driveItem resource represents a file, folder, image, or other item stored in An API request to get all items from a personal OneDrive returns a heterogenous collection with different facets populated. In the following example, there is a folder, a file, and an image in the collection. The image entity has two facets populated: file and image. ``` -https://graph.microsoft.com/v1.0/me/drive/root/children +GET https://graph.microsoft.com/v1.0/me/drive/root/children Response shortened for readability: diff --git a/graph/patterns/flat-bag.md b/graph/patterns/flat-bag.md index aded64e8..d3d71fd2 100644 --- a/graph/patterns/flat-bag.md +++ b/graph/patterns/flat-bag.md @@ -2,32 +2,52 @@ Microsoft Graph API Design Pattern -### *A known pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with all the potential properties plus an additional property to distinguish the variants.* +*A known pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with all potential properties plus an additional property to distinguish the variants.* ## Problem -API designer needs to model a small and limited number of variants of a common concept with a concise list of non-overlapping properties and consistent behavior across variant. The designer also wants to simplify query construction. +API designers need to model a small and limited number of variants of a common concept with a concise list of non-overlapping properties and consistent behavior across variant. The designer also wants to simplify query construction. ## Solution -The API designer creates one entity type with all the potential properties plus an additional property to distinguish the variants, often called `variantType`. +The API designer creates one entity type with all the potential properties plus an additional property to distinguish the variants, often called `variantType`. For each value of `variantType` some properties are meaningful and others are ignored. -## Issues and Considerations +## When to use this pattern -???? +The flat-bag pattern is useful when there is a small number of variants with similar behavior and variants are queried for mostly read-only operations. +The pattern also makes it syntactically easier to query resources by using OData $filter expression because it doesn't require casting. +## Issues and considerations -## When to Use this Pattern +In general the flat-bag pattern is the least recommended modeling choice because it is weakly typed and it is difficult to semantically verify targeted resource modifications. But there are circumstances when query simplicity and limited number of properties may overweight considerations of more strongly typed approach. +The pattern is not recommended for large number of variants and properties because the payload becomes sparsely populated. -The flat-bag pattern is useful when there is a small number of variants - -???? - -There are related patterns to consider such as -[Type Hierarchy](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Flat -bag of -properties](https://github.com/microsoft/api-guidelines/tree/graph/graph). +You can consider related patterns such as Type hierarchy and Flat bag of properties. ## Example -????? + +A good example for flat-bag implementation is the recurrencePattern and recurrenceRange types on [patternedRecurrence](https://docs.microsoft.com/graph/api/resources/patternedrecurrence). + +The recurrencePattern has six variants expressed as six different values of the `type` property (for example: daily, weekly, ...). The key here is that for each of these values, some properties are meaningful and others are ignored (for example: `daysOfWeek` is relevant when `type` is `weekly` but not when it is `daily`). + +``` + + + + + + + + + + + + + + + + + + +``` \ No newline at end of file From 471eb081d3ece7f7acb82f47a57c6f4786a2354a Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 30 Jun 2022 12:38:26 -0400 Subject: [PATCH 419/729] update on hybrid --- graph/GuidelinesGraph.md | 10 +++-- graph/patterns/change-notification.md | 30 +++++++++++++ graph/patterns/flat-bag.md | 8 ++-- graph/patterns/operations.md | 65 ++++++++++++++------------- 4 files changed, 73 insertions(+), 40 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 437093a3..86b93b1b 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -12,6 +12,7 @@ Table of contents - [Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) - [Query support](#query-support) - [Resource modeling patterns](#resource-modeling-patterns) + - [Pros and cons](#pros-and-cons) - [Behavior modeling](#behavior-modeling) - [Error handling](#error-handling) - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) @@ -215,7 +216,7 @@ The three most often used patterns in Microsoft Graph today are type hierarchy, - **[Facets](./patterns/facets.md)** are represented by a single entity type with common properties and one facet property (of complex type) per variant. The facet properties only have a value when the object represents that variant. -- **Flat bag of properties** is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required or meaningful for the variant given by the type property. +- **[Flat bag of properties]/(./patterns/flat-bag.md)** is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required or meaningful for the variant given by the type property. The following table shows a summary of the main qualities for each pattern and can help you select a pattern fit for your use case. @@ -223,7 +224,7 @@ The following table shows a summary of the main qualities for each pattern and c |-------------------------|-----------------------------------------------|---------------------------------------------------|---------------------------| | Type hierarchy | yes | no | no | | Facets | partially | yes | yes | -| Flat | no | no | yes | +| Flat bag | no | no | yes | #### Pros and cons @@ -235,11 +236,11 @@ Following are a few pros and cons to decide which pattern to use: - Introducing new cases/variants in **[facets](./patterns/facets.md)** is straightforward. You need to be careful because it can introduce situations where previously only one of the facets was non-null and now all the old ones are null. This is not unlike adding new subtypes in the **hierarchy** pattern or adding a new type value in the **flat** pattern. -- **hierarchy** and **facets** (to a slightly lesser degree) are well-suited for strongly typed client programming languages, whereas **flat** is more familiar to developers of less strongly typed languages. +- **hierarchy** and **facets** (to a slightly lesser degree) are well-suited for strongly typed client programming languages, whereas **flat bag** is more familiar to developers of less strongly typed languages. - **facets** has the potential to model what is typically associated with multiple inheritance. -- **facets** and **flat** lend to syntactically simpler filter query expression. **hierarchy** is more explicit but requires the cast segments in the filter query. +- **facets** and **flat bag** lend to syntactically simpler filter query expression. **hierarchy** is more explicit but requires the cast segments in the filter query. - **hierarchy** can be refined by annotating the collections with OData derived type constraints; see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Validation.V1.md). This annotation restricts the values to certain sub-trees of an inheritance **hierarchy**. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return objects of a type that are semantically not suitable. @@ -379,6 +380,7 @@ The guidelines in previous sections are intentionally brief and provide a jump s | [Dictionary](./patterns/dictionary.md) | Clients can provide an unknown quantity of data elements of the same type. | | [Evolvable enums](./patterns/evolvable-enums.md) | Extend enumerated types without breaking changes. | | [Facets](./patterns/facets.md) | Model parent-child relationships. | +| [Flat bag](./patterns/flat-bag.md) | Model variants of the same type. | | [Modeling subsets](./patterns/subsets.md) | Model collection subsets for All, None, Included, or Excluded criteria. | | [Namespace](./patterns/namespace.md) | Organize resource definitions into a logical set. | | [Type hierarchy](./patterns/subtypes.md) | Model `is-a` relationships using subtypes. | diff --git a/graph/patterns/change-notification.md b/graph/patterns/change-notification.md index e69de29b..09f6a814 100644 --- a/graph/patterns/change-notification.md +++ b/graph/patterns/change-notification.md @@ -0,0 +1,30 @@ +# Change notification + +Microsoft Graph API Design Pattern + +*Provide a short description of the pattern.* + + +## Problem + +*Describe the business context relevant for the pattern.* + +*Provide a short description of the problem.* + +## Solution + +*Describe how to implement the solution to solve the problem.* + +*Describe related patterns.* + +## When to use this pattern + +*Describe when and why the solution is applicable and when it might not be.* + +## Issues and considerations + +*Describe tradeoffs of the solution.* + +## Example + +*Provide a short example from real life.* \ No newline at end of file diff --git a/graph/patterns/flat-bag.md b/graph/patterns/flat-bag.md index d3d71fd2..279653c4 100644 --- a/graph/patterns/flat-bag.md +++ b/graph/patterns/flat-bag.md @@ -1,4 +1,4 @@ -# Flat Bag Pattern +# Flat Bag of properties Pattern Microsoft Graph API Design Pattern @@ -15,19 +15,19 @@ The API designer creates one entity type with all the potential properties plus ## When to use this pattern -The flat-bag pattern is useful when there is a small number of variants with similar behavior and variants are queried for mostly read-only operations. +The Flat bag pattern is useful when there is a small number of variants with similar behavior and variants are queried for mostly read-only operations. The pattern also makes it syntactically easier to query resources by using OData $filter expression because it doesn't require casting. ## Issues and considerations -In general the flat-bag pattern is the least recommended modeling choice because it is weakly typed and it is difficult to semantically verify targeted resource modifications. But there are circumstances when query simplicity and limited number of properties may overweight considerations of more strongly typed approach. +In general the Flat bag pattern is the least recommended modeling choice because it is weakly typed and it is difficult to semantically verify targeted resource modifications. But there are circumstances when query simplicity and limited number of properties may overweight considerations of more strongly typed approach. The pattern is not recommended for large number of variants and properties because the payload becomes sparsely populated. You can consider related patterns such as Type hierarchy and Flat bag of properties. ## Example -A good example for flat-bag implementation is the recurrencePattern and recurrenceRange types on [patternedRecurrence](https://docs.microsoft.com/graph/api/resources/patternedrecurrence). +A good example for Flat bag implementation is the recurrencePattern type on [recurrencePattern](https://docs.microsoft.com/en-us/graph/api/resources/recurrencepattern?view=graph-rest-1.0). The recurrencePattern has six variants expressed as six different values of the `type` property (for example: daily, weekly, ...). The key here is that for each of these values, some properties are meaningful and others are ignored (for example: `daysOfWeek` is relevant when `type` is `weekly` but not when it is `daily`). diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index 09db0c2a..b807efb2 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -2,33 +2,31 @@ Microsoft Graph API Design Pattern -  +*Operations pattern provides an ability to model a change which impacts multiple resources and can't be effectively modeled using HTTP methods* -### *Operations pattern provides an ability to model a change which impacts multiple resources and can't be effectively modeled using HTTP methods* - -
    ## Problem ---- -Sometimes when modeling a complex business domain API designers need to model a business operation which effects multiple resources and needs to be performed as a single unit. Modeling the operation via HTTP methods on each individual resource may be either ineffective or doesn't reflect how it's proccessed by the backend service. In addition the operation may produce an observable side effects. -* * +Sometimes when modeling a complex business domain API designers need to model a business operation which effects multiple resources and needs to be performed as a single unit. Modeling the operation via HTTP methods on each individual resource may be either ineffective or doesn't reflect how it's processed by the backend service. In addition the operation may produce an observable side effects. ## Solution --------- To address these use cases an API designers may use operational resources such as functions or actions. If the operation doesn't have any side effects and MUST return a single instance of a type or collection of instances then the designer SHOULD use OData function. otherwise the designer can model operation as an action. +## When to use this pattern + +The operation pattern is well suited to use cases which cannot be modeled as a single HTTP method on a resource and require either multiple round trips to complete a single logical operation or produce one or multiple side effects. + +There are related patterns to consider such as -* * +[Long running operation](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Change Tracking](https://github.com/microsoft/api-guidelines/tree/graph/graph). -## Issues and Considerations ---- +## Issues and considerations - - MS Graph does NOT support unbound actions or functions. +- MS Graph does NOT support unbound actions or functions. Bound actions and functions are invoked on resources matching the type of the binding parameter. The binding parameter can be of any type, and it MAY be Nullable. For MS Graph, actions and functions must have the `isBound="true"` attribute. The first parameter is the binding parameter. @@ -44,27 +42,30 @@ Bound actions and functions are invoked on resources matching the type of the bi - API designer **MUST** use POST to call operations on resources. | - - Addition of a new mandatory not nullable parameter to an existing action or function is a breaking change and is not allowed without proper versioning [](). - - -## When to Use this Pattern - -The operation pattern is well suited to use cases which cannot be modeled as a single HTTP method on a resource and require either multiple round trips to complete a single logical operation or produce one or multiple side effects. - -There are related patterns to consider such as - -[Long running operation](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Change Tracking](https://github.com/microsoft/api-guidelines/tree/graph/graph). - - -* * + - Addition of a new mandatory not nullable parameter to an existing action or function is a breaking change and is not allowed without proper versioning [](). ## Example -------- - -*Provide a short example from real life* - -* *  - - +``` + + + + + +``` + + + + + + + + + + + + + + + From 14c8147d9f43491ae730e5a139c6c3ea07ec16a2 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 30 Jun 2022 12:45:48 -0400 Subject: [PATCH 420/729] flat bag examples --- graph/GuidelinesGraph.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 86b93b1b..e5ef4372 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -381,6 +381,7 @@ The guidelines in previous sections are intentionally brief and provide a jump s | [Evolvable enums](./patterns/evolvable-enums.md) | Extend enumerated types without breaking changes. | | [Facets](./patterns/facets.md) | Model parent-child relationships. | | [Flat bag](./patterns/flat-bag.md) | Model variants of the same type. | +| [Long running operations](./patterns/longRunningOperations.md)| Model operations where processing a client request takes a long time. | | [Modeling subsets](./patterns/subsets.md) | Model collection subsets for All, None, Included, or Excluded criteria. | | [Namespace](./patterns/namespace.md) | Organize resource definitions into a logical set. | | [Type hierarchy](./patterns/subtypes.md) | Model `is-a` relationships using subtypes. | From 5936a33ce1a4e659f02f114fcf5435b6f3716af8 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Thu, 30 Jun 2022 14:48:39 -0700 Subject: [PATCH 421/729] Edited Olga's latest updates --- graph/GuidelinesGraph.md | 8 +- graph/patterns/alternate-key.md | 2 +- graph/patterns/evolvable-enums.md | 2 +- graph/patterns/facets.md | 2 +- graph/patterns/flat-bag.md | 18 ++-- ...erations.md => long-running-operations.md} | 84 +++++++++---------- graph/patterns/operations.md | 41 ++++----- graph/patterns/subtypes.md | 2 +- 8 files changed, 72 insertions(+), 87 deletions(-) rename graph/patterns/{longRunningOperations.md => long-running-operations.md} (66%) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e5ef4372..e1368dd6 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -216,7 +216,7 @@ The three most often used patterns in Microsoft Graph today are type hierarchy, - **[Facets](./patterns/facets.md)** are represented by a single entity type with common properties and one facet property (of complex type) per variant. The facet properties only have a value when the object represents that variant. -- **[Flat bag of properties]/(./patterns/flat-bag.md)** is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required or meaningful for the variant given by the type property. +- **[Flat bag of properties](./patterns/flat-bag.md)** is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required or meaningful for the variant given by the type property. The following table shows a summary of the main qualities for each pattern and can help you select a pattern fit for your use case. @@ -234,7 +234,7 @@ Following are a few pros and cons to decide which pattern to use: - Introducing new cases in **hierarchy** is relatively isolated (which is why it is so familiar to OOP) and is considered backwards compatible (at least syntactically). -- Introducing new cases/variants in **[facets](./patterns/facets.md)** is straightforward. You need to be careful because it can introduce situations where previously only one of the facets was non-null and now all the old ones are null. This is not unlike adding new subtypes in the **hierarchy** pattern or adding a new type value in the **flat** pattern. +- Introducing new cases/variants in **[facets](./patterns/facets.md)** is straightforward. You need to be careful because it can introduce situations where previously only one of the facets was non-null and now all the old ones are null. This is not unlike adding new subtypes in the **hierarchy** pattern or adding a new type value in the **[flat bag](./patterns/flat-bag.md)** pattern. - **hierarchy** and **facets** (to a slightly lesser degree) are well-suited for strongly typed client programming languages, whereas **flat bag** is more familiar to developers of less strongly typed languages. @@ -380,8 +380,8 @@ The guidelines in previous sections are intentionally brief and provide a jump s | [Dictionary](./patterns/dictionary.md) | Clients can provide an unknown quantity of data elements of the same type. | | [Evolvable enums](./patterns/evolvable-enums.md) | Extend enumerated types without breaking changes. | | [Facets](./patterns/facets.md) | Model parent-child relationships. | -| [Flat bag](./patterns/flat-bag.md) | Model variants of the same type. | -| [Long running operations](./patterns/longRunningOperations.md)| Model operations where processing a client request takes a long time. | +| [Flat bag](./patterns/flat-bag.md) | Model variants of the same type. | +| [Long running operations](./patterns/longRunningOperations.md)| Model operations where processing a client request takes a long time. | | [Modeling subsets](./patterns/subsets.md) | Model collection subsets for All, None, Included, or Excluded criteria. | | [Namespace](./patterns/namespace.md) | Organize resource definitions into a logical set. | | [Type hierarchy](./patterns/subtypes.md) | Model `is-a` relationships using subtypes. | diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 3754e702..5bec7466 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -153,7 +153,7 @@ Declare `mail` and `ssn` as alternate keys on an entity: } ``` -4. Request a resource where the alternate key property does not exist on any resource in the colleciton: +4. Request a resource where the alternate key property does not exist on any resource in the collection: ```http GET https://graph.microsoft.com/v1.0/users(email='unknown@contoso.com') diff --git a/graph/patterns/evolvable-enums.md b/graph/patterns/evolvable-enums.md index 11d12918..0039f300 100644 --- a/graph/patterns/evolvable-enums.md +++ b/graph/patterns/evolvable-enums.md @@ -2,7 +2,7 @@ Microsoft Graph API Design Pattern -*The evolvable enum pattern allows API producers to extend enumerated types with new members without breaking API consumers.* +*The evolvable enums pattern allows API producers to extend enumerated types with new members without breaking API consumers.* ## Problem diff --git a/graph/patterns/facets.md b/graph/patterns/facets.md index e4238239..ae1244a8 100644 --- a/graph/patterns/facets.md +++ b/graph/patterns/facets.md @@ -19,7 +19,7 @@ In this solution, a child variant is identified by the presence of one or more f The facet pattern is useful when there is a number of variants and they are not mutually exclusive. It also makes it syntactically easier to query resources by using OData $filter expression because it doesn't require casting. -You can consider related patterns such as [Type hierarchy](./subtypes.md) and Flat bag of properties. +You can consider related patterns such as [type hierarchy](./subtypes.md) and [flat bag of properties](./flat-bag.md). ## Issues and considerations diff --git a/graph/patterns/flat-bag.md b/graph/patterns/flat-bag.md index 279653c4..5dedbeef 100644 --- a/graph/patterns/flat-bag.md +++ b/graph/patterns/flat-bag.md @@ -1,33 +1,31 @@ -# Flat Bag of properties Pattern +# Flat bag of properties Microsoft Graph API Design Pattern *A known pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with all potential properties plus an additional property to distinguish the variants.* - ## Problem -API designers need to model a small and limited number of variants of a common concept with a concise list of non-overlapping properties and consistent behavior across variant. The designer also wants to simplify query construction. +API designers need to model a small and limited number of variants of a common concept with a concise list of non-overlapping properties and consistent behavior across variants. The designer also wants to simplify query construction. ## Solution -The API designer creates one entity type with all the potential properties plus an additional property to distinguish the variants, often called `variantType`. For each value of `variantType` some properties are meaningful and others are ignored. +The API designer creates one entity type with all the potential properties plus an additional property to distinguish the variants, often called `variantType`. For each value of `variantType`, some properties are meaningful and others are ignored. ## When to use this pattern -The Flat bag pattern is useful when there is a small number of variants with similar behavior and variants are queried for mostly read-only operations. -The pattern also makes it syntactically easier to query resources by using OData $filter expression because it doesn't require casting. +The flat bag pattern is useful when there is a small number of variants with similar behavior, and variants are queried for mostly read-only operations. The pattern also makes it syntactically easier to query resources by using the OData `$filter` expression because it doesn't require casting. ## Issues and considerations -In general the Flat bag pattern is the least recommended modeling choice because it is weakly typed and it is difficult to semantically verify targeted resource modifications. But there are circumstances when query simplicity and limited number of properties may overweight considerations of more strongly typed approach. -The pattern is not recommended for large number of variants and properties because the payload becomes sparsely populated. +In general, the flat bag pattern is the least recommended modeling choice because it is weakly typed, and it is difficult to semantically verify targeted resource modifications. However, there are circumstances when query simplicity and a limited number of properties might overweight considerations of a more strongly typed approach. +The pattern is not recommended for a large number of variants and properties because the payload becomes sparsely populated. -You can consider related patterns such as Type hierarchy and Flat bag of properties. +You can consider related patterns such as [type hierarchy](./subtypes.md) and [facets](./facets.md). ## Example -A good example for Flat bag implementation is the recurrencePattern type on [recurrencePattern](https://docs.microsoft.com/en-us/graph/api/resources/recurrencepattern?view=graph-rest-1.0). +A good example for flat bag implementation is the recurrencePattern type on [recurrencePattern](https://docs.microsoft.com/graph/api/resources/recurrencepattern). The recurrencePattern has six variants expressed as six different values of the `type` property (for example: daily, weekly, ...). The key here is that for each of these values, some properties are meaningful and others are ignored (for example: `daysOfWeek` is relevant when `type` is `weekly` but not when it is `daily`). diff --git a/graph/patterns/longRunningOperations.md b/graph/patterns/long-running-operations.md similarity index 66% rename from graph/patterns/longRunningOperations.md rename to graph/patterns/long-running-operations.md index 0440237d..689cde02 100644 --- a/graph/patterns/longRunningOperations.md +++ b/graph/patterns/long-running-operations.md @@ -2,7 +2,7 @@ Microsoft Graph API Design Pattern -__*The long running operations (LRO) pattern provides the ability to model operations where processing a client request takes a long time, but the client isn't blocked and can do some other work until operation completion.*__ +*The long running operations (LRO) pattern provides the ability to model operations where processing a client request takes a long time, but the client isn't blocked and can do some other work until operation completion.* ## Problem @@ -10,7 +10,9 @@ The API design requires modeling operations on resources, which takes a long tim to complete so that API clients don't need to wait and can continue doing other work while waiting for the final operation results. The client should be able to monitor the progress of the operation and have an ability to cancel it if -needed. The API needs to provide a mechanism to track the work +needed. + +The API needs to provide a mechanism to track the work being done in the background. The mechanism needs to be expressed in the same web style as other interactive APIs. It also needs to support checking on the status and/or being notified asynchronously of the results. @@ -24,8 +26,7 @@ operation. There are two flavors of this solution: - The returned resource is the targeted resource and includes the status of - the operation. This pattern is often called RELO (resource-based - long running operation). + the operation. This pattern is often called RELO (resource-based long running operation).

    @@ -33,9 +34,7 @@ There are two flavors of this solution:

    -- The returned resource is a new API resource called 'Stepwise Operation' and - is created to track the status. This LRO solution is similar to the concept - of Promises or Futures in other programming languages. +- The returned resource is a new API resource called *stepwise operation* and is created to track the status. This LRO solution is similar to the concept of Promises or Futures in other programming languages.

    @@ -47,32 +46,28 @@ The RELO pattern is the preferred pattern for long running operations and should used wherever possible. The pattern avoids complexity, and consistent resource presentation makes things simpler for our users and tooling chain. -In general, Microsoft Graph API guidelines for LRO follow [Microsoft REST API +In general, Microsoft Graph API guidelines for long running operations follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#13-long-running-operations). There are some deviations from the base guidelines where Microsoft Graph API standards require that you do one of the following: -- For RELO pattern you should return the Location header that indicates the location of the resource. +- For the RELO pattern, you should return the Location header that indicates the location of the resource. - The API response says the targeted resource is being created by returning a 201 status code and the resource URI is provided in the Location header, but the response indicates that the request is not completed by including "Provisioning" status. -- For LRO pattern you should return the Location header that indicates the location of a new stepwise operation resource. +- For the LRO pattern, you should return the Location header that indicates the location of a new stepwise operation resource. - The API response says the operation resource is being created at the URL provided in the Location header and indicates that the request is not completed by including a 202 status code. - - Microsoft Graph doesn’t allow tenant wide operation resources therefore stepwise operations is often modeled as a navigation property on the target resource. + - Microsoft Graph doesn’t allow tenant-wide operation resources; therefore, stepwise operations are often modeled as a navigation property on the target resource. ## When to use this pattern -Any API call that is expected to take longer than 1 second in the 99th percentile should use the long running operations pattern. +Any API call that is expected to take longer than one second in the 99th percentile should use the long running operations pattern. -How do you select which flavor of LRO pattern to use? An API designer can follow these -heuristics: +How do you select which flavor of LRO pattern to use? An API designer can follow these heuristics: -1. If a service can create a resource with a minimal latency and continue - updating its status according to the well-defined and stable state - transition model until completion, then the RELO model is the best choice. +1. If a service can create a resource with a minimal latency and continue updating its status according to the well-defined and stable state transition model until completion, then the RELO model is the best choice. -2. Otherwise, a service should follow the Stepwise Operation pattern. +2. Otherwise, a service should follow the stepwise operation pattern. - ## Issues and considerations - One or more API consumers MUST be able to monitor and operate on the same resource at the same time. @@ -82,32 +77,28 @@ heuristics: resource is no longer active. Clients MAY issue a GET on some resource to determine the state of a long running operation. -- The long running operations pattern SHOULD work for clients looking to "fire and forget" +- The long running operations pattern SHOULD work for clients looking to "fire and forget" and for clients looking to actively monitor and act upon results. -- The long running operations pattern might be supplemented by the [change notification - pattern](change-notification.md). +- The long running operations pattern might be supplemented by the [change notification pattern](./change-notification.md). -- Cancellation of a long running operation does not explicitly mean a rollback. On a per API-defined case, it - might mean a rollback, or compensation, or completion, or partial completion, +- Cancellation of a long running operation does not explicitly mean a rollback. On a per API-defined case, it + might mean a rollback or compensation or completion or partial completion, etc. Following a canceled operation, the API should return a consistent state that allows continued service. -- A recommended minimum retention time for a stepwise operation is 24 hours. +- A recommended minimum retention time for a stepwise operation is 24 hours. Operations SHOULD transition to "tombstone" for an additional period of time prior to being purged from the system. -- Services that provides a new operation resource MUST support GET semantics on the operation. -- Services that returns a new operation MUST always return an LRO (even if the LRO is created in the completed state) that way API consumers don't have to deal with two different shapes of response. - - +- Services that provide a new operation resource MUST support GET semantics on the operation. +- Services that return a new operation MUST always return an LRO (even if the LRO is created in the completed state); that way API consumers don't have to deal with two different shapes of response. ## Examples +### Create a new resource using RELO -### Create a new resource using RELO - -A client wants to provision a new database +A client wants to provision a new database: ``` POST https://graph.microsoft.com/v1.0/storage/databases/ @@ -119,7 +110,7 @@ POST https://graph.microsoft.com/v1.0/storage/databases/ The API responds synchronously that the database has been created and indicates that the provisioning operation is not fully completed by including the -Content-Location header and status property in the response payload. +Content-Location header and status property in the response payload: ``` HTTP/1.1 201 Created @@ -132,7 +123,8 @@ Location: https://graph.microsoft.com/v1.0/storage/databases/db1 [ … other fields for "database" …] } ``` -The client waits for a period of time then invokes another request to try to get the database status. + +The client waits for a period of time, and then invokes another request to try to get the database status: ``` GET https://graph.microsoft.com/v1.0/storage/databases/db1 @@ -146,10 +138,9 @@ HTTP/1.1 200 Ok } ``` +### Cancel RELO operation -### Cancel RELO operation - -A client wants to cancel provisioning of a new database +A client wants to cancel provisioning of a new database: ``` DELETE https://graph.microsoft.com/v1.0/storage/databases/db1 @@ -159,7 +150,7 @@ DELETE https://graph.microsoft.com/v1.0/storage/databases/db1 The API responds synchronously that the database is being deleted and indicates that the operation is accepted and is not fully completed by including the status property in the response payload. The API might provide a -recommendation to wait for 30 seconds. +recommendation to wait for 30 seconds: ``` HTTP/1.1 202 Accepted @@ -172,14 +163,15 @@ Retry-After: 30 [ … other fields for "database" …] } ``` -The client waits for a period of time then invokes another request to try to get the deletion status. + +The client waits for a period of time, and then invokes another request to try to get the deletion status: ``` GET https://graph.microsoft.com/v1.0/storage/databases/db1 HTTP/1.1 404 Not Found ``` -### Create a new resource using the Stepwise Operation +### Create a new resource using the stepwise operation ``` POST https://graph.microsoft.com/v1.0/storage/archives/ @@ -191,7 +183,7 @@ POST https://graph.microsoft.com/v1.0/storage/archives/ ``` The API responds synchronously that the request has been accepted and includes -the Location header with an operation resource for further polling. +the Location header with an operation resource for further polling: ``` HTTP/1.1 202 Accepted @@ -200,7 +192,7 @@ Location: https://graph.microsoft.com/v1.0/storage/operations/123 ``` -### Poll on a Stepwise Operation +### Poll on a stepwise operation ``` @@ -208,7 +200,7 @@ GET https://graph.microsoft.com/v1.0/storage/operations/123 ``` The server responds that results are still not ready and optionally provides a -recommendation to wait 30 seconds. +recommendation to wait 30 seconds: ``` HTTP/1.1 200 OK @@ -220,6 +212,7 @@ Retry-After: 30 "status": "running" } ``` + The client waits the recommended 30 seconds and then invokes another request to get the results of the operation. @@ -241,7 +234,8 @@ HTTP/1.1 200 OK "resourceLocation": "https://graph.microsoft.com/v1.0/storage/archives/987" } ``` -### Trigger a long running action using the Stepwise Operation + +### Trigger a long running action using the stepwise operation ``` POST https://graph.microsoft.com/v1.0/storage/copyArchive @@ -254,7 +248,7 @@ POST https://graph.microsoft.com/v1.0/storage/copyArchive ``` The API responds synchronously that the request has been accepted and includes -the Location header with an operation resource for further polling. +the Location header with an operation resource for further polling: ``` HTTP/1.1 202 Accepted diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index b807efb2..d7e83385 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -1,48 +1,41 @@ -# Operations pattern +# Operations Microsoft Graph API Design Pattern -*Operations pattern provides an ability to model a change which impacts multiple resources and can't be effectively modeled using HTTP methods* - +*The operations pattern provides the ability to model a change that impacts multiple resources and can't be effectively modeled by using HTTP methods.* ## Problem -Sometimes when modeling a complex business domain API designers need to model a business operation which effects multiple resources and needs to be performed as a single unit. Modeling the operation via HTTP methods on each individual resource may be either ineffective or doesn't reflect how it's processed by the backend service. In addition the operation may produce an observable side effects. +Sometimes when modeling a complex business domain, API designers need to model a business operation that effects multiple resources and needs to be performed as a single unit. Modeling the operation via HTTP methods on each individual resource might be either ineffective or not reflect how it's processed by the backend service. In addition, the operation might produce observable side effects. ## Solution -To address these use cases an API designers may use operational resources such as functions or actions. -If the operation doesn't have any side effects and MUST return a single instance of a type or collection of instances then the designer SHOULD use OData function. -otherwise the designer can model operation as an action. +To address these use cases, API designers might use operational resources such as functions or actions. +If the operation doesn't have any side effects and MUST return a single instance of a type or a collection of instances, then the designer SHOULD use the OData function; otherwise, the designer can model the operation as an action. ## When to use this pattern -The operation pattern is well suited to use cases which cannot be modeled as a single HTTP method on a resource and require either multiple round trips to complete a single logical operation or produce one or multiple side effects. - -There are related patterns to consider such as - -[Long running operation](https://github.com/microsoft/api-guidelines/tree/graph/graph) and [Change Tracking](https://github.com/microsoft/api-guidelines/tree/graph/graph). +The operations pattern is well suited to use cases that cannot be modeled as a single HTTP method on a resource and require either multiple round trips to complete a single logical operation or produce one or multiple side effects. +You can consider related patterns such as [long running operations](./long-running-operations) and [change tracking](./change-tracking). ## Issues and considerations -- MS Graph does NOT support unbound actions or functions. +- Microsoft Graph does NOT support unbound actions or functions. Bound actions and functions are invoked on resources matching the type of the binding parameter. The binding parameter can be of any type, and it MAY be Nullable. For Microsoft Graph, actions and functions must have the `isBound="true"` attribute. The first parameter is the binding parameter. -Bound actions and functions are invoked on resources matching the type of the binding parameter. The binding parameter can be of any type, and it MAY be Nullable. For MS Graph, actions and functions must have the `isBound="true"` attribute. The first parameter is the binding parameter. - - - Both actions and functions support overloading, meaning a schema may contain multiple actions or functions with the same name.The overload rules as per the OData [standard](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionOverloads) apply when adding parameters to actions and functions. +- Both actions and functions support overloading, meaning a schema might contain multiple actions or functions with the same name. The overload rules as per the OData [standard](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionOverloads) apply when adding parameters to actions and functions. - - As Graph only supports bound actions and functions, all must have at least one parameter where the first is the binding parameter. The MUSTS of parameters: +- Because Microsoft Graph only supports bound actions and functions, all must have at least one parameter where the first is the binding parameter. The MUSTS of parameters are as follows: + + - Each parameter must have a simple identifier name. + - The parameter name must be unique within the overload. + - The parameter must specify a type. - - Each parameter must have a simple identifier name. - - The parameter name must be unique within the overload. - - The parameter must specify a type. +- Microsoft Graph supports the use of optional parameters. The optional parameter annotation can be used instead of creating function or action overloads when unnecessary. - - Graph supports the use of optional parameters. The optional parameter annotation can be used instead of creating function or action overloads when unnecessary. +- API designer **MUST** use POST to call operations on resources. - - API designer **MUST** use POST to call operations on resources. - | - - Addition of a new mandatory not nullable parameter to an existing action or function is a breaking change and is not allowed without proper versioning [](). +- The addition of a new mandatory not-nullable parameter to an existing action or function is a breaking change and is not allowed without proper versioning. ## Example diff --git a/graph/patterns/subtypes.md b/graph/patterns/subtypes.md index 26cbda52..c5c6a098 100644 --- a/graph/patterns/subtypes.md +++ b/graph/patterns/subtypes.md @@ -22,7 +22,7 @@ subtype for each variant of the resource. In the hierarchy, the interdependencie Use this pattern where each variant of a common concept has its own unique properties and behaviors, no combination of variants is anticipated, and it is acceptable that callers who need to query resources by variant are adequately served by filtering or partitioning using type casting. -Related patterns are [Facets](./facets.md) and Flat bag of properties. +You can consider related patterns such as [facets](./facets.md) and [flat bag of properties](./flat-bag.md). ## Issues and considerations From de5b67d23acb1505e95b975b18b9d1e633097f07 Mon Sep 17 00:00:00 2001 From: Linda Caputo <27295799+Linda-Editor@users.noreply.github.com> Date: Thu, 30 Jun 2022 15:08:48 -0700 Subject: [PATCH 422/729] Fixed typos in my last commit --- graph/patterns/facets.md | 4 ++-- graph/patterns/long-running-operations.md | 7 +++---- graph/patterns/operations.md | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/graph/patterns/facets.md b/graph/patterns/facets.md index ae1244a8..949d573a 100644 --- a/graph/patterns/facets.md +++ b/graph/patterns/facets.md @@ -17,7 +17,7 @@ In this solution, a child variant is identified by the presence of one or more f ## When to use this pattern -The facet pattern is useful when there is a number of variants and they are not mutually exclusive. It also makes it syntactically easier to query resources by using OData $filter expression because it doesn't require casting. +The facets pattern is useful when there is a number of variants and they are not mutually exclusive. It also makes it syntactically easier to query resources by using the OData `$filter` expression because it doesn't require casting. You can consider related patterns such as [type hierarchy](./subtypes.md) and [flat bag of properties](./flat-bag.md). @@ -27,7 +27,7 @@ When introducing a new facet, you need to ensure that the new facet doesn't chan ## Example -The driveItem resource represents a file, folder, image, or other item stored in a drive and is modeled by using entity type with multiple facets. +The driveItem resource represents a file, folder, image, or other item stored in a drive and is modeled by using an entity type with multiple facets. ```XML diff --git a/graph/patterns/long-running-operations.md b/graph/patterns/long-running-operations.md index 689cde02..1492e938 100644 --- a/graph/patterns/long-running-operations.md +++ b/graph/patterns/long-running-operations.md @@ -7,7 +7,7 @@ Microsoft Graph API Design Pattern ## Problem The API design requires modeling operations on resources, which takes a long time -to complete so that API clients don't need to wait and can continue doing other +to complete, so that API clients don't need to wait and can continue doing other work while waiting for the final operation results. The client should be able to monitor the progress of the operation and have an ability to cancel it if needed. @@ -48,7 +48,6 @@ presentation makes things simpler for our users and tooling chain. In general, Microsoft Graph API guidelines for long running operations follow [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#13-long-running-operations). - There are some deviations from the base guidelines where Microsoft Graph API standards require that you do one of the following: - For the RELO pattern, you should return the Location header that indicates the location of the resource. @@ -214,7 +213,7 @@ Retry-After: 30 ``` The client waits the recommended 30 seconds and then invokes another request to get -the results of the operation. +the results of the operation: ``` GET https://graph.microsoft.com/v1.0/storage/operations/123 @@ -222,7 +221,7 @@ GET https://graph.microsoft.com/v1.0/storage/operations/123 The server responds with a "status:succeeded" operation that includes the resource -location. +location: ``` HTTP/1.1 200 OK diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index d7e83385..362f1766 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -17,7 +17,7 @@ If the operation doesn't have any side effects and MUST return a single instance The operations pattern is well suited to use cases that cannot be modeled as a single HTTP method on a resource and require either multiple round trips to complete a single logical operation or produce one or multiple side effects. -You can consider related patterns such as [long running operations](./long-running-operations) and [change tracking](./change-tracking). +You can consider related patterns such as [long running operations](./long-running-operations.md) and [change tracking](./change-tracking.md). ## Issues and considerations From b46c94dfb75a0b29f5ce3e55868e91de45081823 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 10 Jul 2022 17:52:19 -0400 Subject: [PATCH 423/729] problem and solution --- graph/patterns/navigation-property.md | 42 +++++++++++++++++++++ graph/patterns/navigation-property/index.md | 41 -------------------- 2 files changed, 42 insertions(+), 41 deletions(-) create mode 100644 graph/patterns/navigation-property.md delete mode 100644 graph/patterns/navigation-property/index.md diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md new file mode 100644 index 00000000..607f50d9 --- /dev/null +++ b/graph/patterns/navigation-property.md @@ -0,0 +1,42 @@ +# Navigation Property + +Microsoft Graph API Design Pattern + +*A navigation property is used to identify a relationship between two resources.* + +## Problem +-------- + +Resources often contain information that identifies other related resources. Usually that information is contained in a returned representation as an id value. In order for a client to access the related resource it must request the primary resource, read the id value of the related resource and then construct a URL to the related resource using the Id value. This requires at least two round trips and requires the client know how to construct the URL to the related resource. + +## Solution +-------- + +Navigation properties are an OData convention that allows an API designer to create a special kind of property in a model that references an related entity. In the HTTP API this property name translates to a path segment that can be appended to the URL of the primary resource in order to access a representation of the related resource. This prevents the client from needing to know any additional information on how to construct the URL to the related resource and the client does not need to retrieve the primary resource if it is only interested in the related resource. It is the responsibility of the API implementation to determine the Id of the related resource and return the representation of the related entity. + +Additionally, using the OData Expand query parameter, related entities can be transcluded into the primary entity so both can be retrieved in a single round trip. + +## Issues and Considerations +------------------------- + +In the current Microsoft Graph implementation, support for navigation properties is limited to entities within the same backend service or the user entity. +  +Implementing support for accessing the "$ref" of a navigation property allows a caller to return just the URL of related resource. e.g. `/user/23/manager/$ref`. This is useful when a client wishes to identity the related resource but doesn't need all of its properties. + +## When to Use this Pattern +------------------------ + +The use of navigation properties is preferred over + +  + +## Example +------- + +*Provide a short example from real life* + +  + +  + +  diff --git a/graph/patterns/navigation-property/index.md b/graph/patterns/navigation-property/index.md deleted file mode 100644 index 50ce9c39..00000000 --- a/graph/patterns/navigation-property/index.md +++ /dev/null @@ -1,41 +0,0 @@ -# Navigation Property - -Microsoft Graph API Design Pattern - -*A navigation property is used to identify a relationship between two resources.* - -## Problem --------- - -Resources often contain information that identifies other related resources. Often that information is contained in a returned representation as an id value. In order for a client to access the related resource it must request the primary resource, read the id value of the related resource and then construct a URL to the related resource using the Id value. This requires at least two round trips and requires the client know how to construct the URL to the related resource. - -## Solution --------- - -Navigation properties are an OData convention that allows an API designer to create a special property in a model that references an related entity. In the HTTP API this property name translates to a path segment that can be appended to the URL of the primary resource in order to access a representation of the related resource. This prevents the client from needing to know how to construct the URL to the related resource and the client does not need to retrieve the primary resource if it is only interested in the related resource. It is the responsibility of the API implementation to determine the ID of the related resource and return the representation of the related entity. - -Additionally, using the OData Expand query parameter, related entities can be transcluded into the primary entity so both can be retrieved in a single round trip. - -## Issues and Considerations -------------------------- - -Cross workload expands don't work. -  - -## When to Use this Pattern ------------------------- - -*Describe when and why the solution is applicable and when it may not.* - -  - -## Example -------- - -*Provide a short example from real life* - -  - -  - -  From f13e0dcfb6e19eec6318f44c768e6372e2185e33 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 20 May 2022 09:50:55 -0500 Subject: [PATCH 424/729] Update Azure API Guidelines for new LRO pattern. --- azure/ConsiderationsForServiceDesign.md | 258 +++++++++++++++++------- azure/Guidelines.md | 94 ++++++--- azure/relo.drawio | 1 - azure/relo.jpg | Bin 20362 -> 0 bytes azure/statmon.drawio | 1 - azure/statmon.jpg | Bin 26379 -> 0 bytes 6 files changed, 247 insertions(+), 107 deletions(-) delete mode 100644 azure/relo.drawio delete mode 100644 azure/relo.jpg delete mode 100644 azure/statmon.drawio delete mode 100644 azure/statmon.jpg diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 2cf54311..b3ca1f44 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -1,9 +1,12 @@ # Considerations for Service Design + + ## History | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2022-May-20 | Update guidance on long-running operations | | 2022-Feb-01 | Updated error guidance | | 2021-Sep-11 | Add long-running operations guidance | | 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | @@ -26,7 +29,7 @@ It is critically important to design your service to avoid disrupting users as t :white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library **without requiring code changes** ## Azure Management Plane vs Data Plane -*Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review.* +_Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review._ A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used to provision and control the operational state of resources. A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful for provisioning/control and applications. In this case, the operation can appear in both APIs. @@ -37,7 +40,7 @@ Although, best practices and patterns described in this document apply to all HT A great API starts with a well thought out and designed service. Your service should define simple/understandable abstractions with each given a clear name that you use consistently throughout your API and documentation. There must also be an unambiguous relationship between these abstractions. Follow these practices to create clear names for your abstractions: -- Don't invent fancy terms or use fancy words. Try explaining the abstraction to someone that is not a domain expert and then name the abstraction using similar verbage. +- Don't invent fancy terms or use fancy words. Try explaining the abstraction to someone that is not a domain expert and then name the abstraction using similar verbiage. - Don't include "throwaway" words in names, like "response", "object", "payload", etc. - Avoid generic names. Names should be specific to the abstraction and highlight how it is different from other abstractions in your service or related services. - Pick one word/term out of a set of synonyms and stick to it. @@ -55,7 +58,7 @@ The whole purpose of a preview to address feedback by improving abstractions, na ## Focus on Hero Scenarios It is important to realize that writing an API is, in many cases, the easiest part of providing a delightful developer experience. There are a large number of downstream activities for each API, e.g. testing, documentation, client libraries, examples, blog posts, videos, and supporting customers in perpetuity. In fact, implementing an API is of miniscule cost compared to all the other downstream activities. -*For this reason, it is **much better** to ship with fewer features and only add new features over time as required by customers.* +_For this reason, it is **much better** to ship with fewer features and only add new features over time as required by customers._ Focusing on hero scenarios reduces development, support, and maintenance costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A telltale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. @@ -83,7 +86,7 @@ Before releasing your API plan to invest significant design effort, get customer :ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. -:ballot_box_with_check: **YOU SHOULD** consider doing a *code with* exercise in which you actively develop with the customer, observing and learning from their API usage. +:ballot_box_with_check: **YOU SHOULD** consider doing a _code with_ exercise in which you actively develop with the customer, observing and learning from their API usage. :ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. @@ -141,94 +144,205 @@ cannot collide with a resource path that contains user-specified resource ids. Long-running operations are an API design pattern that should be used when the processing of an operation may take a significant amount of time -- longer than a client will want to block waiting for the result. -Azure allows for two forms of this design pattern: resource-based long-running operations (RELO), -which is the preferred pattern, and long-running operations with a status monitor. -In both patterns, the processing of the operation is initiated by one API call and the client -obtains the results of the operation from a subsequent API call. -Here we illustrate the sequence of API calls involved in each of these patterns. +The request that initiates a long-running operation returns a response that points to or embeds +a _status monitor_, which is an ephemeral resource that will track the status and final result of the operation. +The status monitor resource is distinct from the target resource (if any) and specific to the individual +operation request. + +A POST or DELETE operation returns a `202 Accepted` response with the status monitor in the response body. +A long-running POST should not be used for resource create -- use PUT as described below. +PATCH must never be used for LROs -- it should be reserved for simple resource updates. +If a long-running update is required it should be implemented with POST. + +There is a special form of long-running operation initiated with PUT that is described +in [Create (PUT) with additional long-running processing](#create-put-with-additional-long-running-processing). +The remainder of this section describes the pattern for long-running POST and DELETE operations. + +This diagram illustrates how a long-running operation with a status monitor is initiated and then how the client +determines it has completed and obtains its results: + +```mermaid +sequenceDiagram + participant Client + participant API Endpoint + participant Status Monitor + Client->>API Endpoint: POST/DELETE + API Endpoint->>Client: HTTP/1.1 202 Accepted
    Retry-After: 5
    { "id": "22", "status": "NotStarted" } + Client->>Status Monitor: GET + Status Monitor->>Client: HTTP/1.1 200 Ok
    Retry-After: 5
    { "id": "22", "status": "Running" } + Client->>Status Monitor: GET + Status Monitor->>Client: HTTP/1.1 200 Ok
    { "id": "22", "status": "Succeeded" } +``` -### Resource-based long-running operations +1. The client sends the request to initiate the long-running operation. +The initial request could be a PUT, POST, or DELETE method. +The request may contain an `operation-id` header that the service uses as the ID of the status monitor created for the operation. + +2. The service validates the request and initiates the operation processing. +If there are any problems with the request, the service responds with a `4xx` status code and error response body. +Otherwise the service responds with a `202-Accepted` HTTP status code. +The response body is the status monitor for the operation including the ID, either from the request header or generated by the service. +When returning a status monitor whose status is not in a terminal state, the response must also include a `retry-after` header indicating the minimum number of seconds the client should wait +before polling (GETting) the status monitor URL again for an update. +For backward compatibility, the response may also include an `Operation-Location` header containing the absolute URL +of the status monitor resource (without an api-version query parameter). -In the RELO pattern, the resource that is the target of the operation contains a `status` field -that holds the status of an outstanding or last completed operation. -This means that the client can use a standard "get" operation on the resource to determine the -status of an operation it initiated. The flow looks like this: +3. After waiting at least the amount of time specified by the previous response's `Retry-after` header, +the client issues a GET request to the status monitor using the ID in the body of the initial response. +The GET operation for the status monitor is documented in the REST API definition and the ID +is the last URL path segment. - -

    - The RELO flow -

    - +4. The status monitor responds with information about the operation including its current status, +which should be represented as one of a fixed set of string values in a field named `status`. +If the operation is still being processed, the status field will contain a "non-terminal" value, like `NotStarted` or `Running`. -1. The client sends the initial request to the resource to initiate the long-running operation. -This initial request could be a PUT, PATCH, POST, or DELETE method. +5. After the operation processing completes, a GET request to the status monitor returns the status monitor with a status field set to a terminal value -- `Succeeded`, `Failed`, or `Canceled` -- that indicates the result of the operation. +If the status is `Failed`, the status monitor resource contains an `error` field with a `code` and `message` that describes the failure. +If the status is `Succeeded` and the LRO is an Action operation, the operation results will be returned in the `results` field of the status monitor. +If the status is `Succeeded` and the LRO is an operation on a resource, the client can perform a GET on the resource +to observe the result of the operation if desired. -2. The resource validates the request and initiates the operation processing. -It sends a response to client with a `200-OK` HTTP status code (or `201-Created` if the operation -is a create operation) and a representation of the resource where the `status` field is set -to a value indicating that the operation processing has been started. +6. There may be some cases where a long-running operation can be completed before the response to the initial request. +In these cases, the operation should still return a `202 Accepted` with the `status` property set to the appropriate terminal state. -3. The client then issues a GET request to the resource to determine if the operation processing -has completed. +7. The service will auto-purge the status monitor resource after completion (at least 24 hours). +The service may offer DELETE of the status monitor resource due to GDPR/privacy. -4. The resource responds with a representation of the resource. While the operation is still being -processed, the status field will contain a "non-terminal" value, like `Processing`. +### Long-running Action Operations -5. After the operation processing has completed, a GET request from the client will receive a response -where the status field contains a "terminal" value -- `Succeeded`, `Failed`, or `Canceled` -- -that indicates the result of the operation. +An action operation that is also long-running combines the [Action Operations](#action-operations) pattern +with the [Long Running Operations](#long-running-operations) pattern. -A resource may support multiple outstanding RELO operations, where the status field of the resource -indicates the combined status of the outstanding operations. -If a new operation request is received when there is already a long-running operation in progress for a resource, -the service should reject the operation if it is inconsistent with one already in progress. -However, if the new operation is redundant or not inconsistent with the one in progress, -for example a "reboot" operation on a VM that is in the process of rebooting, then the service should -accept the request. The status field of the resource should then report the completion status of _both_ -operations. +The operation is initiated with a POST operation and the operation path ends in `:action`. -Note: The RELO pattern should not be used in cases where the completion status of individual operations -may be important to users, as opposed to simply learning that an operation of the type they requested -(e.g. create a resource with a specific name) has successfully completed. +```text +POST /:action +Operation-Id: 22 + +{ + "arg1": 123 + "arg2": "abc" +} +``` -### Long-running operations with status monitor +The response is a `202 Accepted` as described above. + +```text +HTTP/1.1 202 Accepted +Operation-Location: https:///22 +Retry-After: 5 + +{ + "id": "22", + "status": "NotStarted" +} +``` -In the LRO with status monitor pattern, the status and results of the operation are encapsulated into -a status monitor resource that is distinct from the target resource and specific to the individual -operation request. Here's what the status monitor LRO pattern looks like: +The client will issue a GET to the status monitor to obtain the status and result of the operation. - -

    - The status monitor LRO flow -

    - +```text +GET https:///22?api-version=2022-05-01 +``` -1. The client sends the request to initiate the long-running operation. -As in the RELO pattern, the initial request could be a PUT, PATCH, POST, or DELETE method. +When the operation completes successfully, the result (if there is one) will be included in the `result` field of the status monitor. -2. The resource validates the request and initiates the operation processing. -It sends a response to the client with a `202-Accepted` HTTP status code. -Included in this response is an `Operation-location` response header with the absolute URL of -status monitor for this specific operation. -The response also includes a `Retry-after` header telling the client a minimum time to wait (in seconds) -before sending a request to the status monitor URL. +```text +HTTP/1.1 200 Ok + +{ + "id": "22", + "status": "Succeeded", + "result": { ... } +} +``` -3. After waiting at least the amount of time specified by the previous response's `Retry-after` header, -the client issues a GET request to the status monitor URL. +### Create (PUT) with additional long-running processing -4. The status monitor URL responds with information about the operation including its current status, -which should be represented as one of a fixed set of string values in a field named `status`. -If the operation is still being processed, the status field will contain a "non-terminal" value, like `Processing`. +A special case of long-running operation that occurs often is a PUT operation to create a resource +that involves some additional long-running processing. +One example is a resource requires physical resources (e.g. servers) to be "provisioned" to make the resource functional. +In this case, the request may contain an `operation-id` header that the service will use as +the ID of the status monitor created for the operation. + +```text +PUT /items/FooBar&api-version=2022-05-01 +Operation-Id: 22 + +{ + "prop1": 555, + "prop2": "something" +} +``` + +In this case the response to the initial request is a `201 Created` to indicate that the resource has been created. +The response body contains a representation of the created resource, which is the standard pattern for a create operation. +A status monitor is created to track the additional processing and the ID of the status monitor +is returned in the `Operation-Id` header of the response. +The response may also include an `Operation-Location` header for backward compatibility. +If the resource supports ETags, the response may contain an `etag` header and possibly an `etag` property in the resource. + +```text +HTTP/1.1 201 Created +Operation-Id: 22 +Operation-Location: https://items/operations/22 +etag: "123abc" + +{ + "id": "FooBar", + "etag": "123abc", + "prop1": 555, + "prop2": "something" +} +``` -5. After the operation processing completes, a GET request to status monitor URL returns a response with a status field containing a terminal value -- `Succeeded`, `Failed`, or `Canceled` -- that indicates the result of the operation. -If the status is `Failed`, the status monitor resource must contain an `error` field with a `code` and `message` that describes the failure. -If the status is `Succeeded`, the response may contain additional fields as appropriate, such as results -of the operation processing. +The client will issue a GET to the status monitor to obtain the status of the operation performing the additional processing. -An important distinction between RELO and status monitor LROs is that there is a unique status monitor for each -status monitor LRO, whereas the status of all RELO operations is combined into the status of the resource. -So status monitor LROs are "one-to-one" with their operation status, whereas RELO-style LROs are "many-to-one". +```text +GET https://items/operations/22?api-version=2022-05-01 +``` + +When the additional processing completes, the status monitor will indicate if it succeeded or failed. + +```text +HTTP/1.1 200 Ok + +{ + "id": "22", + "status": "Succeeded" +} +``` + +If the additional processing failed, the service may delete the original resource if it is not usable in this state. + +### Long-running delete operation + +A long-running delete operation follows the general pattern of a long-running operation -- +it returns a `202 Accepted` with a status monitor which the client uses to determine the outcome of the delete. + +The resource being deleted should remain visible (returned from a GET) until the delete operation completes successfully. + +When the delete operation completes successfully, a client must be able to create new resource with same name without conflicts. + +### Controlling a long-running operation + +It might be necessary to support some control action on a long-running operation, such as cancel. +This is implemented as a POST on the status monitor endpoint with `:action` added. + +```text +POST /:cancel +``` + +A successful response to a control operation should be a `200 Ok` with a representation of the status monitor. + +```text +HTTP/1.1 200 OK + +{ + "id": "22", + "status": "Canceled" +} +``` ## Errors One of the most important parts of service design is also one of the most overlooked. The errors returned by your service are a critical part of your developer experience and are part of your API contract. Your service and your customer's application together form a distributed system. Errors are inevitable, but well-designed errors can help you avoid costly customer support incidents by empowering customers to self-diagnose problems. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index b89cc68c..ee857586 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,8 +1,12 @@ # Microsoft Azure REST API Guidelines + + + ## History | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2022-May-20 | Update guidance on long-running operations | | 2022-May-11 | Drop guidance on version discovery | | 2022-Mar-29 | Add guidelines about using durations | | 2022-Mar-25 | Update guideline for date values in headers to follow RFC 7231 | @@ -135,7 +139,7 @@ GET | Read (i.e. list) a resource collection | `200-OK` GET | Read the resource | `200-OK` DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` -:white_check_mark: **DO** return status code `202-Accepted` and follow the guidance in [Long-Running Operations & Jobs](#long-running-operations--jobs) when a PUT, PATCH, POST, or DELETE method completes asynchronously. +:white_check_mark: **DO** return status code `202-Accepted` and follow the guidance in [Long-Running Operations & Jobs](#long-running-operations--jobs) when a PUT, POST, or DELETE method completes asynchronously. :white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase @@ -757,68 +761,92 @@ implemented as a _long-running operation (LRO)_. This allows clients to continue operation is being processed. The client obtains the outcome of the operation at some later time through another API call. See the [Long Running Operations section](./ConsiderationsForServiceDesign.md#long-running-operations) in -Considerations for Service Design for an introduction to the design of long running operations. +Considerations for Service Design for an introduction to the design of long-running operations. :white_check_mark: **DO** implement an operation as an LRO if the 99th percentile response time is greater than 1s. +:no_entry: **DO NOT** implement PATCH as an LRO. If LRO update is required it should be implemented with POST. + In rare instances where an operation may take a _very long_ time to complete, e.g. longer than 15 minutes, it may be better to expose this as a first class resource of the API rather than as an operation on another resource. -There are two basic patterns that can be used for long-running operations: -1. Resource-based long-running operations (RELO) -2. Long-running operations with status monitor +There are two basic patterns for long-running operations in Azure. The first pattern is used for a POST and DELETE +operations that initiate the LRO. These return a `202 Accepted` response with a JSON status monitor in the response body. +The second pattern applies only in the case of a PUT operation to create a resource that also involves additional long-running processing. +These are described in the following two sections. -:white_check_mark: **DO** use the RELO pattern when the operation is on a resource that contains a "status" property that can be used to obtain the outcome of the operation. +#### POST or DELETE LRO pattern -:ballot_box_with_check: **YOU SHOULD** only use the status monitor LRO pattern when the RELO pattern is not applicable. +A POST or DELETE long-running operation accepts a request from the client to initiate the operation processing and returns +a [status monitor](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.3) that reports the operation's progress. -#### Resource-based long-running operations +:no_entry: **DO NOT** use a long-running POST to create a resource -- use PUT as described below. -Some common situations where the RELO pattern should be used: -1. A "create" operation (PUT, PATCH, or POST) for a resource where the basic structure of the resource is created immediately and includes a status field that indicates when the create has completed, e.g. "provisioning" -> "active". -2. An action operation for a resource where both the initiation of the action and the completion of the action cause a change to the "status" property of the resource. +:white_check_mark: **DO** allow the client to pass an `Operation-Id` header with an ID for the operation's status monitor. -:white_check_mark: **DO** return a `200-OK` response, `201-Created` for create operations, from the request that initiates the operation. The response body should contain a representation of the resource that clearly indicates that the operation has been accepted or started. +:white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. -:white_check_mark: **DO** support a get method on the resource that returns a representation of the resource including the status field that indicates when the operation has completed. +:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header that matches an outstanding operation unless the request is identical to the prior request (a retry scenario). -:white_check_mark: **DO** define the "status" field of the resource as an enum with all the values it may contain including the "terminal" values "Succeeded", "Failed", and "Canceled". See [Enums & SDKs](#enums--sdks-client-libraries). +:white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. -:ballot_box_with_check: **YOU SHOULD** use the name `status` for the "status" field of the resource. +:white_check_mark: **DO** return a `202-Accepted` status code from the request that initiates an LRO if the processing of the operation was successfully initiated (except for "create (PUT) with additional processing" type LRO). -#### Long-running operations with status monitor +:warning: **YOU SHOULD NOT** return any other `2xx` status code from the initial request of an LRO -- return `202-Accepted` and a status monitor even if processing was completed before the initiating request returns. -In a long-running operation with status monitor, the client makes a request to initiate the operation processing and receives a URL in the response where it can obtain the operation results. The [HTTP specification](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.3) calls the target of this URL a "status monitor". +:white_check_mark: **DO** include a `Retry-After` header in the response to the initiating request if the operation is not complete. The value of this header should be an integer number of seconds to wait before making the first request to the status monitor. -:white_check_mark: **DO** return a `202-Accepted` status code from the request that initiates an LRO with status monitor if the processing of the operation was successfully initiated. +:ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation, but do not include an api-version query parameter. -:white_check_mark: **DO** perform as much validation of the initial request as practical and return an error response immediately when appropriate (without starting the operation). +:white_check_mark: **DO** return a status monitor in the response body as described in [Status and results of long-running operations](#status-and-results-of-long-running-operations). -:white_check_mark: **DO** return the status monitor URL in the `Operation-Location` response header. +#### Create (PUT) operation with additional long-running processing -:white_check_mark: **DO** support the `get` method on the status monitor endpoint that returns a `200-OK` response with a response body that contains the completion status of the operation with sufficient information to diagnose any potential failures. +For a create (PUT) with additional long-running processing: -:white_check_mark: **DO** include a field in the status monitor resource named `status` indicating the operation's status. This field should be a string with well-defined values. Indicate the terminal state using "Succeeded", "Failed", or "Canceled". +:white_check_mark: **DO** allow the client to pass an `Operation-Id` header with a ID for the status monitor for the operation. -:white_check_mark: **DO** include a field in the status monitor named `error` to contain error information -- minimally `code` and `message` fields -- when an operation fails. +:white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. -:white_check_mark: **DO** retain the status monitor resource for some documented period of time (at least 24 hours) after the operation completes. +:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header that matches an outstanding operation unless the request is identical to the prior request (a retry scenario). + +:white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. + +:white_check_mark: **DO** return a `201-Created` status code from the initial request with a representation of the resource if the resource was created successfully. + +:white_check_mark: **DO** include an `Operation-Id` header in the response with the ID of the status monitor for the operation. -:white_check_mark: **DO** include a `Retry-After` header in the response to the initiating request and requests to the operation-location URL. The value of this header should be an integer number of seconds to wait before making the next request to the operation-location URL. +:white_check_mark: **DO** include response headers with any additional values needed for a GET request to the status monitor (e.g. location). -:heavy_check_mark: **YOU MAY** support a `get` method on the status monitor collection URL that returns a list of status monitors for all recently initiated operations. +:white_check_mark: **DO** include a `Retry-After` header in the response to the initiating request if the operation is not complete. The value of this header should be an integer number of seconds to wait before making the first request to the status monitor. -:warning: **YOU SHOULD NOT** return any other `2xx` status code from the initial request of a status-monitor LRO -- return `202-Accepted` and a status monitor URL even if processing was completed before the initiating request returns. +:ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation, but do not include an api-version query parameter. -:no_entry: **DO NOT** return any data in the response body of a `202-Accepted` response. +#### Obtaining status and results of long-running operations -Previous Azure guidelines specified "Azure-AsyncOperation" as the name of the response header containing the status monitor URL. +For all long-running operations, the client will issue a GET on a status monitor resource to obtain the current status of the operation. -:white_check_mark: **DO** return **both** `Azure-AsyncOperation` and `Operation-Location` headers if your service previously returned `Azure-AsyncOperation`, even though they are redundant, so that existing clients will continue to operate. +:white_check_mark: **DO** support the GET method on the status monitor endpoint that returns a `200-OK` response with the current state of the status monitor. -:white_check_mark: **DO** return the same value for **both** headers. +:white_check_mark: **DO** return a status monitor in the response body that conforms with the following structure: -:white_check_mark: **DO** look for **both** headers in client code, preferring the `Operation-Location` header. +**OperationStatus** : Object + +Property | Type | Required | Description +-------- | ----------- | :------: | ----------- +`id` | string | true | The unique id of the operation +other | | true | Other values needed for a GET request to the status monitor (e.g. location) +`status` | string | true | enum that includes terminal values "Succeeded", "Failed", "Canceled" +`error` | ErrorDetail | | Error object that describes the error when status is "Failed" +`result` | object | | Only for POST action-type LRO, the results of the operation when completed successfully + +:white_check_mark: **DO** include a `Retry-After` header in the response to GET requests to the status monitor if the operation is not complete. The value of this header should be an integer number of seconds to wait before making the next request to the status monitor. + +:white_check_mark: **DO** include the `result` property (if any) in the status monitor for a POST action-type long-running operation when the operation completes successfully. + +:no_entry: **DO NOT** include a `result` property in the status monitor for a long-running operation that is not a POST action-type long-running operation. + +:white_check_mark: **DO** retain the status monitor resource for some documented period of time (at least 24 hours) after the operation completes. ### Bring your own Storage When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. @@ -847,7 +875,7 @@ It is not uncommon to rely on other services, e.g. storage, when implementing yo Generally speaking, there are two patterns that you will encounter when working with files; single file access, and file collections. ##### Single file access -Desiging an API for accessing a single file, depending on your scenario, is relatively straight forward. +Designing an API for accessing a single file, depending on your scenario, is relatively straight forward. :heavy_check_mark: **YOU MAY** use a Shared Access Signature [SAS](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. diff --git a/azure/relo.drawio b/azure/relo.drawio deleted file mode 100644 index 4c6c4a61..00000000 --- a/azure/relo.drawio +++ /dev/null @@ -1 +0,0 @@ -5VhbV6MwGPw1POqBBGj7aFutD7vas8WjPkaIkCMlbBpsu79+g4RLSKW37e6qL20zhA8yM9+QYsDRfDVhKI2+0wDHBjCDlQHHBgAugOIzB9YFYNv9AggZCQrIqoEZ+YUlaEo0IwFeKBM5pTEnqQr6NEmwzxUMMUaX6rRnGqtXTVGINWDmo1hH70nAowLtO2aNX2MSRuWVLVMemaNysgQWEQrosgHBSwOOGKW8+DVfjXCcc1fyUpx39c7R6sYYTvguJ8AkG16t2MBffzPT5PbeA2hyJsV4RXEmFzy980SN6YU3us6/b2eevH2+Ljl5xYwTQdFFTMJEQE+Uczo34BAnwUXOeY7F1H8RULVqMx9wRl8qHi2BPNOES9EtwchQX5RcJw4UneQSJ5jOMWdrMYHhGHHyqsqGpPphNa86dUqJuAQwpVEtKGWSPrVcUy2xoBnzsTyrSXOrkG1vKcQRCzHXCokfjfXU0JuKeyg60BS99rypQEDuTFHT/IGLtRiOEATxrLgv92eWO3E4ZdTHiwVJwhoznPGXMIGm3QAcZgLNTe1Cf84Ek7u4P/UGmZ0CAm8eQkL5zZmlmWAUEyyv1VSR0SwJcCCFWEaE41mK/PzoUuS5wCI+jzfo5HTplHsDrzqVkkd7lsLTQNK2rKO2CtSoEbPQfF9ahct9ibM14jTKGv5OaCLAYYAWUcVhB1+CJrZ+yNvgvBo+yrlvg/FK9kgxWm/smM4OKXy13RlbO6mhgLNBgBI7NnWBqj/sHZi60FULaQ45ccP1tvsmFL2W7t4y1ZYGPZUVzM1SleFlqpmzay+B/qmaCWqkVI+eE+dQp0bbw+mfxY/zUeOni/D/JGvs/rmjZkTb+Dtv8dqV/nLY6Jv2yeUH2aWXcd3Kqt7R+3UArQPFtLcUOrGYX3q//o4dnN7ezd1W0e0dZgfNV+1Cp965m5+uuS2743G5mwpVyWO7Wyt0ajn1f2L7tfcs832M863Rp+luC7jHtjeE7mF+0IzVLnSwH8SwfpdXTK9fiMLL3w== \ No newline at end of file diff --git a/azure/relo.jpg b/azure/relo.jpg deleted file mode 100644 index ffaa7cb71383279c582714077dfc3d9c2f98b1ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20362 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 Zta@0$lqf?`cCEO;Rbltvujj9o{{^+L>>~gG diff --git a/azure/statmon.drawio b/azure/statmon.drawio deleted file mode 100644 index aafa1cdb..00000000 --- a/azure/statmon.drawio +++ /dev/null @@ -1 +0,0 @@ -5Vhrc6IwFP01fHSHEB760Qe1O305xU7bjyxEYIsJG0PV/voNEnnF+traqd12piWH5GLOOTf3igL708WQukl4Q3wUK5rqLxQ4UDTNggb/mwHLHDBUAQQ08nMIlIATvSEBqgJNIx/NahMZITGLkjroEYyRx2qYSymZ16dNSFx/auIGSAIcz41l9DHyWZijbUMt8UsUBeH6yUAVd6buerIAZqHrk3kFgrYC+5QQll9NF30UZ9ytecnXXbxzt/hgFGG2zwKI097Fgna85bWa4LvHseYOW+08yqsbp2LDo4cxjzHqjvuX2f87JxsO7Gt7bIt9sOWanFdEWcS56sZRgDn0izBGpgrsIex3M/IzLCbeC4eK7avZgFHyUhAKODIhmAn1AaemJ+9ObBj5NcHEXoeITBGjSz6Bothl0WtdP1fYICjmFUtHJOKP0FThWGAJgwrDAlP9oVZ+GgFnJKUeEjGq7DfC6lBthq0HYi4NEJMC8YvK7kpoJe4BQnckoS/H4xFH+K+imYoGQSbMXYIoZ4/gFpdtdbGabMZchJ7DXJbO+JobgiNGKL96uL/mdwO2UqyIcp992JY7YYhWlmNcTv0PjNRUXAP6cdYB1o5AH2ed4UPcHo07qZ5oEbx9CiLCbltAsk4/jpB4VlVFSlLsI18IMQ8jhpzE9bK7c14cOBayabxBJ2ObTpk30GKrUuu7oE5URwzn5cFdHM9h5dCG6vva1sg8lDldYk7irGJwTDAHe747CwsStxDGeaLLpywPfhTDZzF3NRgsRJLko+XGlNmaIrmxdltjZypVFDA2CLDG/vXoNuv6w7Zx5GHdMJLkkBNnnFyVh/b4PA7NxTqlGuVOhQer2W6IYMKPKMRm81g24adqu6UQ8w/GY6qKxRVaVdpV7fyTZh1ib0SJh2azCAclpliDfMn3KLjveKeZxbutI2ncOfIgkEpvM9CJzQIls3RHPzlgYz8pnvmVSjBsd75WCTbOvATvnTQdUCfekojfVnkPaGv1HbVx7yLbCAQ061S5dTWnb1cvfeeZQhvc/GbTyZOzoa2Vv998sewy2ubO7AInyq6NHGrfPbs+p3E19Q9qXJuBgHayxnWjH+R6dfaNq6UfqmazcYVQP0XjysN+qrbyl9n9Glcn9TyEsmOz3reesymaL+8Ob0mhdWTZbLakUqCjbcCH5dvofHr5Sh/afwE= \ No newline at end of file diff --git a/azure/statmon.jpg b/azure/statmon.jpg deleted file mode 100644 index 2a7b58c0a8494836122636fcb304614522498396..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26379 zcmeFZWmH_twkX^&5|RJ`LJ}+lXZ}&lK+u$^BLeO+>Yo@m@8Pw#@gjKt>58yi65HTs;gc- zwXQA-fD=FkAPo?^g8xUuKiktT69C}*1pwTN`e&Lk0syG-2LPU-|CvVj2>>AQ0|2TA z?Ts9b{-ZFrukJUYPyk>z7XZN11^|e^0RZ=P|Er+?{Pv%H@fUS{arF(wRXy#le&zsc zfEnNgKo(#NFafY%K_I|O04D%^ISmj8+`9QY{J!713b%1?{|@(XaPHvX-n)Mv_Z}|p z{f7h(??1qQfQySqh=)(`=n>(g`wxkTi5?MO!H<3;x$*nUTet6Befa1B?t`n;|IKju z1weT32J=n1TQ_I`HwkatBD`_g4xs!!c5eZ0{T|x?8SdV}xp(U}?#sJEJzm-?C4R%EQV~2 zVxT;1HU5b#*j84nAD&fU!sN}Fh1}0)eL1Wr2NO4i&)tSfWpN2le-}hX2BfN=fEy0P zv$wX(axMXj7s3}QUkcvEdFDEoZI2YRNKN>3Uxy3Ae8k=El71@hVI9qGD+t(M!GU9Y za-ys>sdnb*Z@O%z4tKRF7MzpopHg3gX($6HT+(Zu&qOkIQa7G|r0$-3A|CN3Khtl6 z=1pxn5i1i%ZDe+Fm+m@`h$2IoA{C7AQSXxOw2_K=U9psEf_&SYnyS?lW4NIve!o2f zT1TY4`rf$Ye8z~lH4`*zU5OnmQQF9ow(_0Ti+i93Q>=E0AaF|1&@yVc=`4iP zQ?Q;nQ}6fVy^11uB-ndG*3{CjYIwN~SWFj378RSM_G!9lq+aU?HfES*)nY1q4Sw_M z$*-P+yRQX3)zQY_m-*XnYtfp*-9h5Ez{zf%Qw)Qe74PFLmy$<%Rc9EMZoNMse}zf? zkK3vw9V=Bh$AuI17i7$wLJh<<^^Os|Qy+0`oO{P*yNi8UZm+pQ<-|>6Nk99$9+HJ^ z7^_EL0z$k!E&&{S9KR^+)&Kaza3cIQT<4UYy}@(ekQ|8PoD$Wsn?YviM`zLG#M{ydKK=dZIn)6b{cs zvJNo%#J&wsmm$f!Ecxlq_S@w5A;8$SU=eZ7*#j_|o-EBK{zyfsVdK!LD-nP2P;td% zE?3fe$(FvNhA;;zc)O#xG z;I#BK-FY)!gv>?rCwgm&;2KQ!Ty#{5r|>iCRq}a-XH%|ub$J=!}<2Of)vhf$iYF3u9}seD4U1Vg&Nx0AJpB#wC%#s`gKm9l_RY1nE5 z>eBY@9tzSzR1i+bHTL`ytFnc-G-(^gSj&ZNHgH|s_gEAm34QG;AX^#Ce_|?vRsRfU zcx(knr9HOPCM~8c*hYor-&>>7tcd)sr9ZSdT6c$}oecL`*lH$Amw7*}8ojnrRX+aF z3S`tD*d~2XI|(kUdE@h6b@gz$PoBdmiso1u3MHEWc5FHP1pgY3AX z)Ic@An14(A_^^57tP?8kTr6#)9YI8R$b=xk+7^V)NheNRhRlG z2*w<_FSE9dQ5L9B{l3XINJF!Yu{zn6M0AOEh_qdC!jaucgw)M~>&$mkwAw0Zu)6a> z?pB(&Ruhu#K-TU~ZtSpi#I3O+F4Bcp@YpUFc!~=D9JCi#0=CHThNvj=Om%JW*caiO zxA5+_pgZHSthfXW;+2SA{7GCV{R*yeO!bYovX&*6{H_^bMxd2?VH=WzJSXWSw^$Hk zcrG>-W=9@D6*TXA&Q5$Tg|HcYvN}p;d^eVl0h-9Iq21=)`Cbt)usW2z`Afr zhidp9wC0%&Y@x{(WGVa9?S}U?miaULEO{HwYEd+pw0zy$&FgFqN{QGh0O@M4UGg-bH zz%B}yf<62cuhDY0JE%4CF9tE~;ODm?<4Mh1wArX71wZ!^`mo+kF%$BzoSRsFO1iWW z9;OioB;kvgbJgV~;Z(y07O+iKiAWJSXZwa_qOoxX)+IPBpVNRR#;&{?<52SmiVo^d zn2DKdXY3xI4e6m~zwP5XiFxv!)Gc_h zLdI<*Q7sL`V;05nSF~TMr|QiP#7&bz@lxlUR(22w&(|R4_=Og8brRvhhNop?8FH`4 z1e0{4<~>I+aCZ4J^;F|o?QA#8wG}Rmf}4A4fI_(b^g_O+aEjJC9P^e&qhjX?S|d=1 zSLK6>0>e@qVWO?UUsRm#?P=k}^gueYza^CaqpIawd9+>tC`fqki(+!X8IzV2dbOMU-U zGCR3#G;7$fWKWbeWJM5JK}&yPKh9cs!q*ABk^9JZDJ~;ZMZP5?IyzyWN~k`5?GoU; zAKp;zlV%@ig8pV~E5$sqQ5VNVi0!o0AyqALtU_i+&(V!SOFBn-5W6NT>#mjHC+g&^4O5&*me zNc-K|P?G&IXQFTk=sWW~=YOwGaS13mxXEqM*i|{x*;-M(G{;l+lV(z7Zag8XXkOA9 z5y@0zwa_fXQyI{&P%opSVa5J1DAv=kvAX^OgKlBkEmgds{v26l%2Hn!JI>}_#?a-+ zvT+Gmuz_*swfv&xx5`>`LBh8F7Qi&YZ`OJm1Mac}KC`>aYg z`U`i{nfGTYCTzZySa`5&hP8M6`aZytu;}P`2kQm~-~ap`U+sgBgm{UVPb76KmyWK3 zVso8RMff)`a4g-!#KE>C?qtkXAMeovKNAd4$zmZ_dbZ zfl|RQhw-f_D0_q=Tk@A2mm2ce6{T>Ihf?$TS*Gai(cW)ytSJn*5~i~2BAy|=K!aWM z+NqP}S#EC=pYXLR-_OI{GzFt?TQ)GjjEj3n>jud9VW!$g0YODznJrZx0~ zmr|O>p9!PL5Tw%YwT%Q+3#C75EaWrI5K=$bbWuK2qh@IJFiZ*xvevb_r-2&NPOFh< z4Js;qUY5;tpItU4j!*Zf7 zQ5JWC#tyJOy1IH;BK8n0VW|`yKRsfusQxz8;1v?JgHr5b40W(GE!2t{UOhFPl7V*- z@D1y2$|+XI?Cx?S2oZ#&OHJ3B{`zQpXr*q-{at36JV32?QPs3|jE!50+`*iQK*Ck! zyJ|2u5iX7jA=|F_NYz9 zLD=$jm`g!KPo-_AMgJg)B~LV51}RTU%IPz+31e@2k{mN;VBfce?=d=QCukSzRl6vO0b*iO9I0DbpFF; z`D1NRf){3OR0{>CIfW*bYMkl_{%?bzbiVXDJIPlxKnP|2o#*%6#fEOH@S zz+<@kb8DwF)pgNh-+)U%9@glp8kc|^-YTU_K=!dxF2PbkurJ(NsLN4zA^zDJ#fR#& z51Dt84IYlQl{z~8fOw?IPw`Tgxx>-5+SJ)(!Sq?OcYb?u{yW?*q`d?@KH0l4Trync zn=J@d91HdhUl$T|R5OVuy-nd$J^x|mj)?*HsQ72c2R|TYX)-h5?d^0q!H*y5LJUO0hpl80lx^!U66Iwr2X9674_%R~0Gejw&+}9a2 zANkQeoSl&2BJX(j^K}-!rp3O{4Ld8TkPaB7t$UrW)|135lxnrDF(s)b>nwe@)Gf#3 zBBl|*9oOH{h(j>M!h?dDx#45UD|X`R!m5d;niG#l6BRj(H1Qv@f&%GNoeVJJ7zf1x z>EUVX%tLC0>UeD>JKRM*#G@a7B~FOGR}43Ipmy~ul8yN(??7Mq8U_7$GeXC=a|s}* z$Pw#-#KJ9l*lOi;eNEaI@j2iewZC*DKG>~uinmgmjYZAH=Y$4&3!OYr-lm+N&@ zXS?PH($S4Jt7E)Z*?+9Ej-DIA9tL0?+NkQ}9AsXfuHitK^KkXN8ANeV93!KoE zZW$CA$r*pWU4W#}pdY85y%UV+^*58)lXK6gxugC2y4tD;pVqNh@ctA5KPi&ChnNkn z1EEkRiiKu@XoN0YMj~utB*voGiW$*DR1ccFScNoGX&mLzIT@8X?iR<^Dc1c`B_nj( zrZJ+_jepfC)NaDhtaULS?n2Y+E(OWonUsn*a;m8JeDIKVVb$3$fA~q#2wBQM0oo zJcjz32CX&!g`&k9)H8u^pL>SRgC7_<<}+nzisUUphf~b_z*(;lgh?K5kkv|MNitf}0s@(kpa=PUyQeIR5R}X359+#c#jac+Jd8mt`B4jO z(--{@YYH~9JV$XP;SBc?@qB&3nXw;SCHU4sK|?(Yc6GN`5k^(TOe(?5Ix#&v-2~Zh z{=x3IAYTp+$jf&11|N4C#@8KDX2Ld!_HfY3)Cw_vGRl_#epiG?_^1MpK~%w*M@|=N zZ3{TyX);5ztI7e6&7m0{;+^}{*Dv|ispmN4Pg3N+#mT<|CU&mOSUeP4>Cdv10ASde zfsrOg+7vZ*|5a{nvLO)bog)RlK{fnVY(jmVtqw71AdxcA)kEGG(#Mw19OU;|;mC?> zS9w=&*WeG3}Vd2M(jiOuyIuHsZe?%^4&kt{s**tk-VE4>08Y^ z2DK3wJ4w>}npe9@&kcj+nUgYee}+n8A}+kT2T{&|;m^WN0qb4~TpgSVizPmHEs{L_ z8oyS@%BY&o5k2-D(9bb2artlT6o(T)7-pqc$8TXQVOdb5-GoFTD~*B$|1>ZEM9AOW z*0pk3I8+qR3n+b{en{adFv9;NXH2`<&Mj2M=doGcOBm?v$hd^0S{^voAB%7;cF}1(iSKrJKjMjwmvy8a-&I_ z)@Bka{5gii^2;q9$M#~~&W5Rn`R z+7ygWSGT)O+(khcM@?ib-M)NlejM1ju?BhVQj$LT73TA zzw~lU$K>L%mB2=5kF08sOJ%hzeQ=v{HtIB}7F);pF|vrA%O)#8sd6Zh{!3%;XBPS1 z&I-QdeW;aTS&gljr)R+e1)8;$4xJ32Gcb6P;)l$BIZ|PhrVL_pn04~mR zm2ZCJp~^reb_FRf!l%a(tgSqEB-r`_e&T1P7WK1O{2E^0Sv_@5DL{bbs$%wAZSsX; z18F+-aCCTykjN~R#&8H{?h_8BIE>CJ>H$rsa8v<$cF%k{IC~LvI*ln_4 z$|*KYif2B|FdAAQrOooU| z3ecZcW?bJceV)K|!PVw&A!$b-e?0IfEF2^p()d&oUid5CiACO`w$r^qCG>?K@;Ssx z4d_~L>L4ouiZxaAZQ2ELeAL7k&3xi3Xzgv(;Xuu@rs!Y5gpwK*BMlM?^2dg??KKS-=p%0FqbYm|~ zF=4FooXphisI#zDtEp_NFvC&&SMkf>*Q%e%ZE98Y#i1>k2j#tZ6pX=m*})8G34y{~ zVSU5W?3r#mwUu=NiOz)#mM?e^wOBd~&*K<1SNULi*!n`lal+tpJ^$MO6c@Kya+{hE z*4|l-obrgH(D&~b!&OI+@winq0$Ip5U|vhgY1moEsAZy$Y6-`dV-kwqMfG5`8aFhW zhFl^+JJAA2WC)_!G_-pTHOJnv`fnlh?|>VF=e&!8&o2Sib_FQ3OW`l>x{t~Zqw*HR zBd-TQYOpt`&^pSq-D&#y&QmzTqY}yaBtEzW568fg{Xv=%N~KG5|3~b) z=XqIkAs1}CbaZ$LPqIsyGfPfEoUcb2`4Pm9b=zB#@-g`YP%Ov$^>INH^wZj+nq}CC ze~z#I+%8%vBI^HrhS*fAf=re8%E^@8Gb5*>?Z6J&l~vD?b*LeVLrN~i(S?PD1$8C0<>{;2 z-BeI7p{#(mCJs-=kV$3sQoxpCr66M83KoEL-KemzS`eo`;1Mf<(f;>B>fX}vBs)+C zt)g=}e2hJ&dye|>0UXGhRHWn(O?77wV<3qYJ`#+S)Zo$Uuq_)j=1~G3$90>?n-=Gg zZxQ?L6~V3K+H`uB8)DP&4499(Hbrc{+~j=f4zqZV1{OTxu~;7l=B;a zKW4)q-I1Bkec)BTla;_*?16YG0tw<>kWzYAdj=j}!@I|EV?t*(sO5WY&tCop(#rK4dzam ztH!7P@J5!MkNJm)<*Z|+wD}EjCf>~4@JHbcL^QLCI5y}H%Djm!UMYS!%!zksv znPF0*Ptxnl6>V?U1Rue$N8Hg4J=+2g0j~(<{u7bJKq=N7( z%jYcA9E-L`;|S9o$lOKOx_d+$xhoM|P}zxOys0OzqqTX)R!|7J7(AT0Ou-O=8I56u z<*=v{6$9oYj!LhoFL#BFv=ii##^r;Pmc_BPg}c#nA{+D}-7ktq9i`C~&|>;bY-sl- zpxf9&|7RCTO?Z?E<;%g`O1~^U*f#;6#z^WoQc}{4b~QNMi!_1G*CBD`^9_```H%}4 zl062wQni{&G6{;{P)e{N-$`MoF!QbgV$h*IvLZ5lTh!5QAd#4=j`FQv%SvvOO<7^g zpq$~KRzH>U<4DPpc}WW);q)-sB#+sC;DvY=;5JzobbrkuRPJkW<$)P5dD_V$BsV6z zocBeyhh2()?ZW*c@OYI7bR};XYD?vDUdp-p<~Ual-6@}+G%bOhObPCmQ%TJl^0pZdQnvE>}zlud?idQb^#61SKhgz z!5--zryh?70DtGhnhLhaO1A)}7*Rs|mqKL)Z=K7(g0rR7oNG<1p6;bPq$fVe`xi<3 z=O}JY;V_9#Dfi@YsyDW7(Au-z0AjW@9NVs=Lx09i>XBE=?jg!4cjXNp@lh??I_Nwp z>d)0_QI$*Nc}gkK8P3M@Z~-V4|FF9u`o|!G^f=<(pYa3!raGWcDwR>UY#cFJayf)A z3zuyXdN3s#%1X|1stFd9%oAMwvAGX!+ySnlSb0 zKMCK!nL2HIF@7$&pcPU8^29if+^;HsTS}FEK~1|-toUJCg=8|Fgi+46F>+~rv3*y{ zJ4fZ=N6XE|8rkL31WF8P1SKtbKXUkwJSVDrX>0Mah&BuOXr7`#!lUV4np0D+XtIVt zO_>QbxN8_*Wu0oseEkOlzLDFN-lWReG7%@E*$0R2eznEXq>P~-HJP|TpmA#fkJk0i zUNr!pfeoJHG_fT);ZdO>Aq=v|@>NC__*Ay}Kt%!qi9?$o)XjF&C(nmEuUU(ISp8Ta zzu{qMER@hP!=>Sq?&+)y$$KPA5X-H}Hj_q8Xi zd(2(Zrk%I!?B<=(2BuwW>!^zO#4&d2>=W66 zmJ)%9VI|KTAc<8kgKwwf+GNPdSq{|VBh}2V3ph$FjH{5htnYH9I|lKX)P2u zj1Cc3tjuOo$ZC3EBayuu!~U=nX)h0t zjlxBDsmVnm%eui@O#`&m2T!;JALqrXK9P7@NLRbl3(w~Q);dv(wj&5w>N;vBA+ten zNh~|}HdSHXM{Q=bS=JCOcUw#jv9C;QHXYoyVU8N_Ft4vpKM3N%HOIt$@8j>STh{|w z4pr=0=qVUo)2uHu1}*G zxXN~=wrI-xEFqcHtXCE>>MYhPK`0^W!FAOCD~!i-YkJbQtH()5mDERPdDb;Jw^j~C z4wLW)D-u#mpDm)zmUxx9$KHw+sxKu|ag{bE@NY!AhxW)fqE!+@uIGjw!lC%>N&Pp2XM%$7O;@YDG{4E#2 zAGfm#)`}`9R%%QQW_Rhk>OV%Hd<3_B&*X6FsDjoEvGe3xDoQ6zbjBs3A+lF)LmCih z_DYfF;fwa!>k}7mI4eqyq*78ENXIOWl^H8$tTSoG+JY9USrX?(Fr6U+k5^ONTB?R& zdhoEY<*qU4?w86=jvDJ~9$P1~WFyV$ylm#6BI&$@dr>yMo91QE5ongper+=zR6l2k zFlrqmD7(_{;l>O6Ql&u~p{oUtVRljHb}wzb(yVY~^@l6tG6+>SJ2<%Y3{P-GF>B;m z;p1lq#E}icE>|mQwF2(_s2U@t*M`EDoN=y+z(Kc%yGH|KM3e1)9{@6!`zkYLi$W|IcO!-{9kyCZ{% zr@WO?_j`!Q4ECwp6bG|SeEW|mr`ja32KyYV7OmHuhoEJPPX#I&-CgukqNLUtxDhJk zdafFQL{o&o@rL1YApAr&{8j#ZXGI{n)_gdpGbY|TNs`-9z7xyQ?oB{Q8%Q*NG2EjE zk6ZFeJYOJ7*EDi*sZ5Nh>x6w$?bna`V{#*aBO#eAFc>V7yAhfri@_1cPqsXuU&eEpPiq=7QM$#G@aWl1j0tdb~? zi_(cFUv*BU3~Ga(5*Z0=93>HICFQtU3r7!j%%19RLYl|WYcOZ0;3Y(HQ#BBur&rH4 z=4CfwNfAYeuhDZBuA-rP3;C3x(!N0}xs@XrFMGzgRAJ{sqEweSGEIPGiM5)vL(tJa z$mj^WBjU$I_rvm=VzhwXuKp#!qVepD6PTy`AiN{ZFnEESs8Dypf{$S7H}JxH&1*g>Bet&>iaam^$TsZc%j}&6;T{LE)vlfP&3Uz$d>;D*R~uQvAOR?ij;WO zF@DtEFB(3j9M2K7;D&f!(qouCz=KRmvfUuepb?MMe&tc3Fb2n!iJKRKsfN(#sFicp zn>HNU8i6aLIoF3bFKV78_<#G(gigw_!npQ32$}YIcM&s9u^6jpOo>^wy>d-X{b3bR z(7taF_a&v=4Rgx7xKYM>+1Umv#<+iqDf7I$>*Hf7gP1hKD0{HL~a|rGLt9j`%Ou=nnc3hMpDsh$=Q+H`Y z&h#7i92jLY=;B0{?Su%Oz7nZO=)^up9=gQyzFQ~d&k8qT>98o}fU1s-qrg;gY*@Yq zE!$$Eq@Xn!F&#%z$GD6IO*)C_@tU`ya@btDT{hVOG{rPcUCe$jW2yT{&+QMf&HGeN z-~D8F2sRuR2XAmluLSSq73Hkz@gHlo$r{Iv4NLK8RL8j?A5xj0W+M$Pf`tfKu3VNc zuiV9gnveFvC6gDBK-WaFEysgrkxO+7U!YvWCPcm)l^Io9It4y8XOx%FZUrZ zIpi#;+oAK(sw_M5{?&{-9!{hDl(Jnyy1V^J$+(%C8qLz?ZYI~{#^Pet?wi25(FjhsKnt*@q>Gg`poXhJ4T@w z$}9QeyMx)QX5yk*6WFG7i7iZzMr*;xttO_<+#de&DQS1~wD!#COF^aY{G$&@K?yn# zwRyr;1ykv-_7eJoVV2(d{$u0A#sbTvIq#KtT-A3zjlJx102U)F>+r5RE>LYc;su8N zF-!+;{uM!hY0@pqEdz6ZcMEIo59KmJiN|q{BJkwh(-Q;l4IK=hwky_4_wbbiSz_4# za7sBZuA4O-pArBIvbS}C_1PO0pB3aajdNz#;Uz$Yj_J}Q;xbj?LzCH{_+%Px#)h!` zCRTlG)DQWIWluqFl+8$d0OCm5b@jr7TRB6it&bX-why;ga?bZnij~Uhz5~8MHEBQOC-TRd!uh&@S zY1<-+$Z^f$RWAYl_g)dH82CCo!e?(?FEn?K8y@E&YXub(#~Hs=q!x;CTOrwUleeo$ z^GsqKbbGqLE+P^C(Qs~36iq$y1o;F?~JF1qi+y`#%Kejv;(k=m2hsW>2oR|5tzE=BN3#CyC z$23rZB|H?5Y$-I=VYFonFsMYi>~_h>Y-Gg4xAngH58D{0Y%G(RPlP{`FnOg9zLXGg z-p0V_eJHT?5LloZY=e%AS*#&)19)eN=?Nw8{*H|s2e9&6{i<2h$AO-NHy0dtdO(!U zD2zjw+EB5ja=v~q7ng`qdZXPpmRWT%;(u`|P>an6QBMn`9N*0s%dcPU!Jq?I22DDnD>hVE*Eesi@Ul zUDQVIO=2Apb)~EeBH)8EaF2$4@nB~O>hlNq^S?}Q{~Z0_u)E1L?$&(%9tmG^&jFoP z4Sv}mR1P=$_w@#rEi#+bD>_gdp`ZIdK-!!|OE&&S1id`+!N4B@D$&Yf~Jwl%!sQ_RP3%bJ=IhCxz=A@;jqNHc#7j4OP;`n+dvIn|J%wHo@W zd2ujV!9&xGm%7t*o}lxK4ZSc<@Ix_G6c1EY%ioZaS-x{oDr%@UQw24=Qj1m{Zyx%k zbi`ZGY&LoF=voc`;LlQWUV8Zq=WA?=I#TJBFatiV zsdsM|nMtKg_XgvT1*=5WvSlpC!yM$Wk#&5X`*&uj!i`Lfr%YwQa>!*55Lcb;|y;U#pR!b4CB7V=WH5)!hhYQ%m^Bq<# zR-M#@Sj zf9VQ}G(0M?35p|R0u?#3KB2AdC6a;V+f#Zou@hXBb&#er4B|+r8(N&?o{^6kg4Vt7 z31_XQZ`-MR^_A_vAcDCMtg!j1oJ5wQKxpD>v7d48waC?8$IM4}shu2nG4*k$!-_C^ z)emZFd+r>HH~j!L>dIMF@38qITAO>TZr6HbW|i?RvG;)TCMfHu-X=#)u{&@}w|?{E zi`|Y#g_bv6qjnHk;QO^neUH8C+dFYK7q;gus0xh9)3FAjg0xfxlVBb{xfsAyg`RXY z=f0e7f2SBhn?>4AyiLB8eveMv*YEW{ML*Qt+sAo3zU%u{L6))+I?q#3B0|wT2iit~ zhRdk__}B2$I>SAJuD@vQ-+K!Ge~}frB1~;naPS0!TIZlqG&jM9&q$iZkH$%eY0;nY zAdVT2**!R@b%OHcLVA}$BOo=j!qSv8M(k-98lnM{%?moF;eyEb4SqQF6aO1aY+v;M z_!p`A=jh+uLh<+a6#N-I;D6NFA_NH~RZ-?qM)TuU=R~c1Y8dvvax*|qyTgWKKo?EO z3kji5|3d$-qCE1VOcPHJ>lC2G0{N$wy0<&JqwzB3ics7{!5j!eH;$S}F0Ng2t-sY> zFP{7rnSZ4JE$x^2_ZAty{0phSt^EW5Fs)-tg{(lpl*z@*(Zw2wSaHWSyhiQ=e^~l$JwxngY??y$*IY>xl%^uP0 ziCmpT~IAc8vozBahZembyK&AgT5Lh5C~ zs&7uLCa-OC#xM9Aq1^>u#$EKTnH@Eg$~5q(DJL7*PMRGF2WAg$y?FeMY>Bw+*HT}p z&ksHG#7#{cmLF0+>cA}GB-qTbW&pArNu-X>PKTunky8C4fA()G@#mz!KfeF>_RRc) z%8kG0Of*S9vFmjezL@->j;LJIT!lH|WG>_-4k2?gQcUxPzapMG3Lfv#igI3^6y-cT zaDwF{(V8aXPs>ffR<`v`4ns7umw;It6-rp9=p`UIn1iEHvAGV+(ds&O)<^OyisUs> zX^BGUc50O`sKeV-GpQCHuT#lJdS5sd%^348=%#ir_#n~9Z}ANpr+GH|B${*I>&s1oovCK*4$&&92Z}U zF+O9kF+`OaYTr=5@qe@a4_ZU|gYC~LJh7-XZ>9~Y(SZ2uqICxoIt1Y{!uQz~$9^IC zLdA4)WmQ=$t9i_5KGia-FB!D%WFVTZ{zW()X>=n?Ia-j~7Yd5fZzQa>Ax)>ZWjvxi z`TRe>J>}2B@$V^);J>$6`)AZQ!p-Ts=$NMnckfTAi!Fb&l!-@2+)5)d*zmntH+A+Q z4s!C}_6~(KFVNnA-{7fSHP2b)H@mr&$7W^7I0b#h$-?q(B#pQjIi7&dl{wWwf2&N# zWal%p%XD`YG#A$EzB(w)7uJXY(a!FX&F_M+krxdo(YrVEZb^nVb33o+PFmUk*_0?6l!6IiFXT(&y4v*ZFEr!Zo z9l6bLyhV45K#;TD5>pHvWXcU{h>LN__i<848Ka^A!c{`sxv-5fgEQjYo2p@4J_C#g z(i-0}0pSOQ>YU}V3ucGKY)&&Ub^=sr#mc6YJK*^w?do!6AWY=ch$s5+v;U)V(zOb==w$Hww>I!^i32lkszal?3a<#-UZm3>zS6;4q> z*0TWaCnxW|4J@*JP5NGYe^+XWz0P^1hdKq@3+ccAW^zYJ%syT!M%vDSl+>a7$2)*~ zW^X81&~Z-IQXD_Q$+ogw=ZWExl!26ZF97iJ=)x>$G0y>`$&^nxI6BC z%9qpBUf|H{zGP`jVl5$UReSM^^hIc$^tqZBA{~JUe+R&cd%v;HYl5vyV+w0wbJ`Y^ z7{LIS@G3LKk{RwrWnDOKfcD7qk}R93)eH6%A`ufaaw!L0jgr_L0~;oJNo1y7+pC16 z&=1QodyoIsMe{%GlzH1QXtJo2tE;^<2`y-xbd8b;FMzmYaMWSVXS^|a-}DGc!GRQ~ zlC2NxKzL_&`!1s2UIJD%-@3f=){dLi7_a1$u=ujx$cUp-|$#MPxsTBSwr!d2Qsme__M38YA-4sjsj zQ`o3Mm(uCgQQ=LFbAJM(KR)Q|!~7o83#x}ebIj%v?zY7?`YIiThhEdE1lQ&wx1Zh~ zo|;*VEGx^O>g3gaUuvVL5QA4_(`BnsTOI5boZlO(P=TqAX$|2o1`fjZT0=gLY(mdXQQuDOL}gBx;pc?pZrD)>Wl&?rL-o6CF~KNfE9*jj>YPyH5-r)LMN_*FDIlU2n3)tYvt9I6K@lLHW&@%GK&$ zELxR3oxVr1o-YYpPOk3})$Qjwpt0Opl!^EhXGLG^ugRuLF3O8f_JW6#S(z$8O6dhE z93uORrUvJ$P84!Ku5vVIunx!dS$>jI1V6`#d0+S7CBXhc^}S!YZB)k0V)V{#Q9^9GOOM3$rL2`9$L?;l%(9I#+XUJpBToy zZA%oHX;uAPQ(a?`4nNY>)84>QyyUYhUpajKhl8yU(oHYjq1cuU!nGWuymuHW{h-fL z?=ERUJLgRF!HR}*#l9sL($HzfPx=81AJ%tWk>f@RQ48gv;IRoJfAHs@l_%?))I5_= z@Hf||>9aF4GhR$rj&n%O=7y1|4r~a*ge51iSH4rB*Ml zHz1**BRrdYr@RD6+mhx@Rn<{>!rTN>SY`h3I*?kC4RW>KP`43hbN;Snd4&bW5=Ydy z&Z)Y`QF{qETi#-Sa0xIvXZ!kD#VbM9Q$BKV^K*x9>5of**CpUJ#rF!gX^*jpFUh5c z+4pL*^^!^7KmUpfP_U7xPAP$7gNmm+A(wzaR*nf|s?!o{;aEOA0Uv%j@A`hi=-3Gcg+5N*wWU{Go0-|vB_A%Nd*^Z@bh9ozcUgP0N zK`Q=j_mEii)vFyn+)`pX61=4DG2elTuIa%u-l=@WP3gt$Ld|ejc#2(G2W0>=G|lIN z;Ak-FB7SaCB1_vDQH2Ycy*g(YG~bv`<#xV%D~j_RnJn{#%Z;f%ID-`yU*^I-q&KFkc7Zdx5PxKv1&b#C=Sf{o)7~a3Fmt6j^IH1{A*rxHCew04N2(n-)R?G^WS-^MSQ08KT(_|*CXlZI5M*2x2aRr+u0 zS3*K&I5dH^y}DjZX%iR~RFtV{mNH=iJ>9Xv&)%d_es0XE_mWoe3|r}Qr#n#{8J!xk zP*k6Wfy2OPw1QVO5z&(v>@+0y@V4calJzw)OygEItFys0(A`sS^y6Nz01*ot3$+;^ z#vL3rTvhlL-fP>S7PIptj?7anoAFx~_jwela7$iQoJbTJ(b1lKbyj#z6MuS&J3YI; zRWHJ>_Edp1J9b_Jtr2_35JwjZUxpW0388J(U#lZ@)$O@-+=m_jbKk$jW>%}N$K+d2 zp!E)4^hh3_pE;KB^AC8(n38jmc@p%UNKKdLBa5r#1IETdy};@?r`dXodRkq`Y_JU1 zhPHPcVKyp~g}@BTa{zuaBp{BqKv+~yux-t|*T>FPe-L{xu+|+6#KLE5@kDz;MsCG)Iva{x-KxmR1$-hZE1Pu0W#7<9vub!v z3HgZBRP4HApUMi#b#Nb$T@69wrtgh=U80>Hvv(Qx7XnQMACPucjgvQWL1e~sQWa^0 zZU5M^wmO6kgjsnV^JFjN3%LtL6C&v{McShZeirACvM*#cVxD>5G1+}wU!=`hsA84ol5dt z$(kAp<~S6a=}F%`2T6|OoN)rwmq&b;(*Z2U?YPdVGNTR z2m&(5OcDboAeaz>fdGa{6Pr;6OBn)4fG`;b5eP#BL?A>aM-YJokT3`V90;Ne4u}W{ z(l=f23-8tCy?$MNZ}q)(*QZtIoL%eGu3fv%TKm6#2l)N_Z`A~%2TJkf=J2B9)PZfC z7dhfLZnE@Y7$=Zn<26w1^l5`(lO!}P)g~G==Eg-4W}x~_llo`*#2>@vUKrRkS(eKoIi!9G3eMt5gBUOWd<*5jw@8kvO%;BSA5 zNy1&}nhjV&?V|Bo*Town%jDKfc26mg%w)n>&Vd#Y7cifrJ+qB|zligHyw*RtZ&mb| z=X;K{OV&jcezm)RbXTsrsEM}ncS6csLHkxzX))G&M3q2J4K zTH3C_qCA_od*(XJf3n+~JY`9vnF4xLW4?T#X(ZqK?#m-l`of~M1N(6i=GME8R z_^0+U&>U`-MB4Z;+KmiCTwtct*V#AhJ6Y)oZP%l9l_9b)Ey8rAmr}=Y1~jdhpn}Io zc{hox z!Td{3uCGrQIQu4E&F!g;GlkJR2KnAU*N*OSvs>4@f*e2B(xj{_RSZ#QU!Hj;uSX5i z;Y$N~XTB;jS*NFQWW3IR=g7Q?ur|+MrL0cm$(Pk0SBW!Dsb=5u%}eWF)49bK>ddTx zyl(zcdK-?%^=;hP<)Pq-?W#L&X~n3jhUo(RC^=z-AjTxF?wAOZ+ECd5RiQu5+(D^z z?;pf=;%1@i*7UZ7EotDvE(Uz`X&D~3xSQiw`E$;$xEAD8v4 zlZLe=tWpXa&`21w{ZRIU*zQE9%+@~ zbp6-IfDK+NkL2TEMVW)aMG46MA^{>NvxeP%@`}fvPc0a+``h>NCJ+yYxTJH`qpICs zL-OQV&N;qe;z*apQfo-pFL@sQ*UwJMQOxkgDwV5|t>2S$%Wu(fOq%J}2ke6pZ$MI4 z+BLnsFNo@xly!Bybp^48d0i?@*~Gp2l|6evphFFpV>vCeyAIXg8Ik=S^z)G=XNJ@l z=^;jSS`*>QBr3>Kksu+X3WM%jyLP{8as9rfU1G5E^|Sl1*Z`Ba;JovY*s-bM5d&gU zEgZOdWW=i5II*@wDdCKxKN%rArIBdw5lU?m<8BulDLA<%om+z$JWH9Fa`N(e3sYQ4 z@&X&Um#|dehOdjTdu9BpmtEe9=s8kP`B>Yf<_%_dYyG^S8FWM2*JuBb#Cg)*(=V8P z{kXe2BbePG?kH0goqu{ys zcn>2@ye$nIu1KKkK&Lvn^_KU$65KEIISEzyA5p$E&@nYpNhv}u#ytrw6D|daZxN9R zAvos*4D$A5CsxoMLYF1Pv5&)G7oecgxBlx2l>vtb+ zfvFgj5^<1<_Bid=ax%sILnbC9x64Im=N;eq0#`_Ax~UwGd=1c|k<=|(A4lqzpatlZ ztid&Mx~M;HbW`y@wzfC{T>S6ZUnCpIQ0iP*hvs7KvaqfLLlyxOS;mo>c9h2=8{Bae z73r~{C#(B=+F%QK5jmI-95aVq%vwW~sC=npQi@8ZZNlzrRK7oi%Da#V1)!y%07QhM`g7H`7Xg zlb&qC?rui#{w5~a{OD%?FEmW{o^WgFQ@#8e^Q)SE^Lj!LPfi!0MYA`^+`RLT0T7KQ zXkUu}MXm;;JITNHo%g!sR8#g69^&AJvd{bZwZ*FjHqRR0n-2}1($HnP~Sf> zk|{%z7PcaE1y87*zW#dkUM!=h_c5N6gB{bd(4U=pLIQPh1&wNnVj67bz*ln9RBL?s z7KqkE1{^JS?%8ZwRD(66&otLV!NwHi5~s>gm(a%!w;(fM(ek!^ak8mRFtaca;89Qt z5Y_yO->+S-oq7-pzA!Pb&bS|2;#@XHWc9SeC~Gj4m0L*r{wV>@W)&h6p|Az3bjMEW zFExrlYl4g01AL6khKy!>_e}X5n&yvOi=8Yof87RyK%MPI_;A+jo}dd4m|lH>T*@AE z4Xvd4RJF1a>XLP30QNo%SjCF2)+v)rU1pZEYxfJBLLTbMnOp_)c++&5og6}3@> z8}n}8VtkJd;j-;ZvSgi2;;VOC!JF4EdN#sM=mvH#cu2mVTdaddi}SIqc+%TzCcdPS z`&wfFMSZ0C5S;tN&dOf#hf(|MCx?FBCUOjA-B=^BZjX=ExUsN1ljri%vn2&v6?E;6 zFhQA5tqoxp0dc zHxk{}+F-jdN(K2hW`62B6oLOCOJ+aieq(!lzqmF~4T-TL6B~-!S1Ve2VCE5pbU0Wn z-W0qPNI9{FobC&WWU$F|pH)rHo7D$NUw#~pZl%U^sQ_J(k8gonF0ycR<<6X{a>6p< z+5${=6#V03BeAMpy%{9Nkz6Xf3PLx!hN||Zul|%@N$1Ipu;c1k@x($~zj(IXZNHg! zI+?pKmV8&Rq*m0@oN5?15RbmIJxwju8l4{ zoO7GRAe|QPoUyj9vm4Wy9#>^C8?zLNp+G;^7B6m~P)CW8r2jm6PW^0eRVypm`#{-C z>l`KIK|fd$1^-;k@PQ~IF-t#-p_40eML->I4uvU-8drV^44qHdLWZ1yiM?k(un zSX_VHOtI;ja-1ZV-kES?*YKv-x6p(8`x}HtU1Wr9 z8beg4U4dXltIo2{8es5k7b7L2JWjM7W|9sK*HV{5V^k{}n~>KQCBETO00&h%*HUrT z-81wNvv4<3Q4>zJseKemp&yq)zi)!g+uo|L~*}S zTd;H2>vYAh4d+9%1Jz1J(g;(}atdLQ>KSID_^Y26U-f<5IQRXMx{F_chfrO#R35Z> z8sV?2>=XZ{Pk$K_*|Y`)H~YJi?)NWttzLh$6{-9;-S$@V@W1>(;a_oBQlx$QSsR~y z2YPH%WSDhq?VD-w ~P*^8fV9A~2%YFxbhl2MH39+{QKOjnf?_=tZUHc Date: Tue, 24 May 2022 04:39:54 -0700 Subject: [PATCH 425/729] Apply suggestions from PR review Co-authored-by: Weidong Xu --- azure/ConsiderationsForServiceDesign.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index b3ca1f44..b67730a6 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -152,7 +152,7 @@ operation request. A POST or DELETE operation returns a `202 Accepted` response with the status monitor in the response body. A long-running POST should not be used for resource create -- use PUT as described below. -PATCH must never be used for LROs -- it should be reserved for simple resource updates. +PATCH must never be used for long-running operations -- it should be reserved for simple resource updates. If a long-running update is required it should be implemented with POST. There is a special form of long-running operation initiated with PUT that is described @@ -176,7 +176,7 @@ sequenceDiagram ``` 1. The client sends the request to initiate the long-running operation. -The initial request could be a PUT, POST, or DELETE method. +The initial request could be a POST or DELETE method. The request may contain an `operation-id` header that the service uses as the ID of the status monitor created for the operation. 2. The service validates the request and initiates the operation processing. @@ -184,7 +184,7 @@ If there are any problems with the request, the service responds with a `4xx` st Otherwise the service responds with a `202-Accepted` HTTP status code. The response body is the status monitor for the operation including the ID, either from the request header or generated by the service. When returning a status monitor whose status is not in a terminal state, the response must also include a `retry-after` header indicating the minimum number of seconds the client should wait -before polling (GETting) the status monitor URL again for an update. +before polling (GETing) the status monitor URL again for an update. For backward compatibility, the response may also include an `Operation-Location` header containing the absolute URL of the status monitor resource (without an api-version query parameter). From c350ae0348cd5c9ef5bbd3fe2138c4486151ef0d Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Tue, 31 May 2022 11:08:09 -0500 Subject: [PATCH 426/729] More fixes for PR comments --- azure/ConsiderationsForServiceDesign.md | 21 +++++++++++---------- azure/Guidelines.md | 10 +++++----- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index b67730a6..43e1cbf8 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -170,9 +170,9 @@ sequenceDiagram Client->>API Endpoint: POST/DELETE API Endpoint->>Client: HTTP/1.1 202 Accepted
    Retry-After: 5
    { "id": "22", "status": "NotStarted" } Client->>Status Monitor: GET - Status Monitor->>Client: HTTP/1.1 200 Ok
    Retry-After: 5
    { "id": "22", "status": "Running" } + Status Monitor->>Client: HTTP/1.1 200 OK
    Retry-After: 5
    { "id": "22", "status": "Running" } Client->>Status Monitor: GET - Status Monitor->>Client: HTTP/1.1 200 Ok
    { "id": "22", "status": "Succeeded" } + Status Monitor->>Client: HTTP/1.1 200 OK
    { "id": "22", "status": "Succeeded" } ``` 1. The client sends the request to initiate the long-running operation. @@ -217,7 +217,7 @@ with the [Long Running Operations](#long-running-operations) pattern. The operation is initiated with a POST operation and the operation path ends in `:action`. ```text -POST /:action +POST /:action?api-version=2022-05-01 Operation-Id: 22 { @@ -248,7 +248,7 @@ GET https:///22?api-version=2022-05-01 When the operation completes successfully, the result (if there is one) will be included in the `result` field of the status monitor. ```text -HTTP/1.1 200 Ok +HTTP/1.1 200 OK { "id": "22", @@ -257,9 +257,9 @@ HTTP/1.1 200 Ok } ``` -### Create (PUT) with additional long-running processing +### PUT with additional long-running processing -A special case of long-running operation that occurs often is a PUT operation to create a resource +A special case of long-running operation that occurs often is a PUT operation to create or replace a resource that involves some additional long-running processing. One example is a resource requires physical resources (e.g. servers) to be "provisioned" to make the resource functional. In this case, the request may contain an `operation-id` header that the service will use as @@ -275,7 +275,8 @@ Operation-Id: 22 } ``` -In this case the response to the initial request is a `201 Created` to indicate that the resource has been created. +In this case the response to the initial request is a `201 Created` to indicate that the resource has been created +or `200 OK` when the resource was replaced. The response body contains a representation of the created resource, which is the standard pattern for a create operation. A status monitor is created to track the additional processing and the ID of the status monitor is returned in the `Operation-Id` header of the response. @@ -305,7 +306,7 @@ GET https://items/operations/22?api-version=2022-05-01 When the additional processing completes, the status monitor will indicate if it succeeded or failed. ```text -HTTP/1.1 200 Ok +HTTP/1.1 200 OK { "id": "22", @@ -330,10 +331,10 @@ It might be necessary to support some control action on a long-running operation This is implemented as a POST on the status monitor endpoint with `:action` added. ```text -POST /:cancel +POST /:cancel?api-version=2022-05-01 ``` -A successful response to a control operation should be a `200 Ok` with a representation of the status monitor. +A successful response to a control operation should be a `200 OK` with a representation of the status monitor. ```text HTTP/1.1 200 OK diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ee857586..366f4b6c 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -786,11 +786,11 @@ a [status monitor](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.3) :white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. -:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header that matches an outstanding operation unless the request is identical to the prior request (a retry scenario). +:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header matches an outstanding operation unless the request is identical to the prior request (a retry scenario). :white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. -:white_check_mark: **DO** return a `202-Accepted` status code from the request that initiates an LRO if the processing of the operation was successfully initiated (except for "create (PUT) with additional processing" type LRO). +:white_check_mark: **DO** return a `202-Accepted` status code from the request that initiates an LRO if the processing of the operation was successfully initiated (except for "PUT with additional processing" type LRO). :warning: **YOU SHOULD NOT** return any other `2xx` status code from the initial request of an LRO -- return `202-Accepted` and a status monitor even if processing was completed before the initiating request returns. @@ -800,9 +800,9 @@ a [status monitor](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.3) :white_check_mark: **DO** return a status monitor in the response body as described in [Status and results of long-running operations](#status-and-results-of-long-running-operations). -#### Create (PUT) operation with additional long-running processing +#### PUT operation with additional long-running processing -For a create (PUT) with additional long-running processing: +For a PUT (create or replace) with additional long-running processing: :white_check_mark: **DO** allow the client to pass an `Operation-Id` header with a ID for the status monitor for the operation. @@ -812,7 +812,7 @@ For a create (PUT) with additional long-running processing: :white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. -:white_check_mark: **DO** return a `201-Created` status code from the initial request with a representation of the resource if the resource was created successfully. +:white_check_mark: **DO** return a `201-Created` status code for create or `200-OK` for replace from the initial request with a representation of the resource if the resource was created successfully. :white_check_mark: **DO** include an `Operation-Id` header in the response with the ID of the status monitor for the operation. From f3e08f259fac114a856d736d6dda0f3024fb74ba Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 8 Jun 2022 07:57:54 -0700 Subject: [PATCH 427/729] Address comments in PR and working session on LRO guidelines --- azure/ConsiderationsForServiceDesign.md | 23 ++++++++++++----------- azure/Guidelines.md | 15 +++++++-------- azure/README.md | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 43e1cbf8..93665849 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -6,7 +6,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | -| 2022-May-20 | Update guidance on long-running operations | +| 2022-Jun-08 | Update guidance on long-running operations | | 2022-Feb-01 | Updated error guidance | | 2021-Sep-11 | Add long-running operations guidance | | 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | @@ -168,7 +168,7 @@ sequenceDiagram participant API Endpoint participant Status Monitor Client->>API Endpoint: POST/DELETE - API Endpoint->>Client: HTTP/1.1 202 Accepted
    Retry-After: 5
    { "id": "22", "status": "NotStarted" } + API Endpoint->>Client: HTTP/1.1 202 Accepted
    { "id": "22", "status": "NotStarted" } Client->>Status Monitor: GET Status Monitor->>Client: HTTP/1.1 200 OK
    Retry-After: 5
    { "id": "22", "status": "Running" } Client->>Status Monitor: GET @@ -177,7 +177,7 @@ sequenceDiagram 1. The client sends the request to initiate the long-running operation. The initial request could be a POST or DELETE method. -The request may contain an `operation-id` header that the service uses as the ID of the status monitor created for the operation. +The request may contain an `Operation-Id` header that the service uses as the ID of the status monitor created for the operation. 2. The service validates the request and initiates the operation processing. If there are any problems with the request, the service responds with a `4xx` status code and error response body. @@ -199,14 +199,15 @@ If the operation is still being processed, the status field will contain a "non- 5. After the operation processing completes, a GET request to the status monitor returns the status monitor with a status field set to a terminal value -- `Succeeded`, `Failed`, or `Canceled` -- that indicates the result of the operation. If the status is `Failed`, the status monitor resource contains an `error` field with a `code` and `message` that describes the failure. -If the status is `Succeeded` and the LRO is an Action operation, the operation results will be returned in the `results` field of the status monitor. +If the status is `Succeeded` and the LRO is an Action operation, the operation results will be returned in the `result` field of the status monitor. If the status is `Succeeded` and the LRO is an operation on a resource, the client can perform a GET on the resource to observe the result of the operation if desired. 6. There may be some cases where a long-running operation can be completed before the response to the initial request. In these cases, the operation should still return a `202 Accepted` with the `status` property set to the appropriate terminal state. -7. The service will auto-purge the status monitor resource after completion (at least 24 hours). +7. The service is responsible for purging the status-monitor resource. +It should auto-purge the status monitor resource after completion (at least 24 hours). The service may offer DELETE of the status monitor resource due to GDPR/privacy. ### Long-running Action Operations @@ -214,10 +215,10 @@ The service may offer DELETE of the status monitor resource due to GDPR/privacy. An action operation that is also long-running combines the [Action Operations](#action-operations) pattern with the [Long Running Operations](#long-running-operations) pattern. -The operation is initiated with a POST operation and the operation path ends in `:action`. +The operation is initiated with a POST operation and the operation path ends in `:`. ```text -POST /:action?api-version=2022-05-01 +POST /:?api-version=2022-05-01 Operation-Id: 22 { @@ -231,7 +232,6 @@ The response is a `202 Accepted` as described above. ```text HTTP/1.1 202 Accepted Operation-Location: https:///22 -Retry-After: 5 { "id": "22", @@ -262,7 +262,7 @@ HTTP/1.1 200 OK A special case of long-running operation that occurs often is a PUT operation to create or replace a resource that involves some additional long-running processing. One example is a resource requires physical resources (e.g. servers) to be "provisioned" to make the resource functional. -In this case, the request may contain an `operation-id` header that the service will use as +In this case, the request may contain an `Operation-Id` header that the service will use as the ID of the status monitor created for the operation. ```text @@ -314,7 +314,8 @@ HTTP/1.1 200 OK } ``` -If the additional processing failed, the service may delete the original resource if it is not usable in this state. +If the additional processing failed, the service may delete the original resource if it is not usable in this state, +but would have to clearly document this behavior. ### Long-running delete operation @@ -328,7 +329,7 @@ When the delete operation completes successfully, a client must be able to creat ### Controlling a long-running operation It might be necessary to support some control action on a long-running operation, such as cancel. -This is implemented as a POST on the status monitor endpoint with `:action` added. +This is implemented as a POST on the status monitor endpoint with `:` added. ```text POST /:cancel?api-version=2022-05-01 diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 366f4b6c..ff86dc6c 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -6,7 +6,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | -| 2022-May-20 | Update guidance on long-running operations | +| 2022-Jun-08 | Update guidance on long-running operations | | 2022-May-11 | Drop guidance on version discovery | | 2022-Mar-29 | Add guidelines about using durations | | 2022-Mar-25 | Update guideline for date values in headers to follow RFC 7231 | @@ -765,7 +765,7 @@ Considerations for Service Design for an introduction to the design of long-runn :white_check_mark: **DO** implement an operation as an LRO if the 99th percentile response time is greater than 1s. -:no_entry: **DO NOT** implement PATCH as an LRO. If LRO update is required it should be implemented with POST. +:no_entry: **DO NOT** implement PATCH as an LRO. If LRO update is required it must be implemented with POST. In rare instances where an operation may take a _very long_ time to complete, e.g. longer than 15 minutes, it may be better to expose this as a first class resource of the API rather than as an operation on another resource. @@ -773,6 +773,7 @@ it may be better to expose this as a first class resource of the API rather than There are two basic patterns for long-running operations in Azure. The first pattern is used for a POST and DELETE operations that initiate the LRO. These return a `202 Accepted` response with a JSON status monitor in the response body. The second pattern applies only in the case of a PUT operation to create a resource that also involves additional long-running processing. +For guidance on when to use a specific pattern, please refer to [Considerations for Service Design, Long Running Operations](./ConsiderationsForServiceDesign.md#long-running-operations). These are described in the following two sections. #### POST or DELETE LRO pattern @@ -794,8 +795,6 @@ a [status monitor](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.3) :warning: **YOU SHOULD NOT** return any other `2xx` status code from the initial request of an LRO -- return `202-Accepted` and a status monitor even if processing was completed before the initiating request returns. -:white_check_mark: **DO** include a `Retry-After` header in the response to the initiating request if the operation is not complete. The value of this header should be an integer number of seconds to wait before making the first request to the status monitor. - :ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation, but do not include an api-version query parameter. :white_check_mark: **DO** return a status monitor in the response body as described in [Status and results of long-running operations](#status-and-results-of-long-running-operations). @@ -818,8 +817,6 @@ For a PUT (create or replace) with additional long-running processing: :white_check_mark: **DO** include response headers with any additional values needed for a GET request to the status monitor (e.g. location). -:white_check_mark: **DO** include a `Retry-After` header in the response to the initiating request if the operation is not complete. The value of this header should be an integer number of seconds to wait before making the first request to the status monitor. - :ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation, but do not include an api-version query parameter. #### Obtaining status and results of long-running operations @@ -835,10 +832,12 @@ For all long-running operations, the client will issue a GET on a status monitor Property | Type | Required | Description -------- | ----------- | :------: | ----------- `id` | string | true | The unique id of the operation -other | | true | Other values needed for a GET request to the status monitor (e.g. location) `status` | string | true | enum that includes terminal values "Succeeded", "Failed", "Canceled" `error` | ErrorDetail | | Error object that describes the error when status is "Failed" `result` | object | | Only for POST action-type LRO, the results of the operation when completed successfully +additional
    properties | | | Additional named or dynamic properties of the operation + +:white_check_mark: **DO** include the `id` of the operation and any other values needed for the client to form a GET request to the status monitor (e.g. a `location` path parameter). :white_check_mark: **DO** include a `Retry-After` header in the response to GET requests to the status monitor if the operation is not complete. The value of this header should be an integer number of seconds to wait before making the next request to the status monitor. @@ -846,7 +845,7 @@ other | | true | Other values needed for a GET request to the :no_entry: **DO NOT** include a `result` property in the status monitor for a long-running operation that is not a POST action-type long-running operation. -:white_check_mark: **DO** retain the status monitor resource for some documented period of time (at least 24 hours) after the operation completes. +:white_check_mark: **DO** retain the status monitor resource for some publicly documented period of time (at least 24 hours) after the operation completes. ### Bring your own Storage When implementing your service, it is very common to store and retrieve data and files. When you encounter this scenario, avoid implementing your own storage strategy and instead use Azure Bring Your Own Storage (BYOS). BYOS provides significant benefits to service implementors, e.g. security, an aggressively optimized frontend, uptime, etc. diff --git a/azure/README.md b/azure/README.md index e92f81a4..cec10619 100644 --- a/azure/README.md +++ b/azure/README.md @@ -7,6 +7,6 @@ Designing powerful APIs with strong defaults, consistent behavior across related * [OpenAPI Style Guidelines](https://github.com/Azure/azure-api-style-guide/blob/main/README.md) * [Breaking Changes](http://aka.ms/AzBreakingChangesPolicy/) Note: Internal Microsoft link -You can reach out to use via [email](mailto://azureapirbcore@microsoft.com) or in our [Teams](https://teams.microsoft.com/l/team/19%3a3ebb18fded0e47938f998e196a52952f%40thread.tacv2/conversations?groupId=1a10b50c-e870-4fe0-8483-bf5542a8d2d8&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) channel. +You can reach out to us via [email](mailto://azureapirbcore@microsoft.com) or in our [Teams](https://teams.microsoft.com/l/team/19%3a3ebb18fded0e47938f998e196a52952f%40thread.tacv2/conversations?groupId=1a10b50c-e870-4fe0-8483-bf5542a8d2d8&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) channel. Note: The Teams channel is internal MS. From d117061e1b4fb5ecef5bed5dfdb30824f17863f0 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Tue, 12 Jul 2022 10:31:18 -0700 Subject: [PATCH 428/729] Revise/improve guidance on api-version in Operation-Location --- azure/Guidelines.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ff86dc6c..09c343d5 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -787,7 +787,7 @@ a [status monitor](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.3) :white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. -:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header matches an outstanding operation unless the request is identical to the prior request (a retry scenario). +:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header matches an existing operation unless the request is identical to the prior request (a retry scenario). :white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. @@ -795,9 +795,13 @@ a [status monitor](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.3) :warning: **YOU SHOULD NOT** return any other `2xx` status code from the initial request of an LRO -- return `202-Accepted` and a status monitor even if processing was completed before the initiating request returns. -:ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation, but do not include an api-version query parameter. +:white_check_mark: **DO** return a status monitor in the response body as described in [Obtaining status and results of long-running operations](#obtaining-status-and-results-of-long-running-operations). -:white_check_mark: **DO** return a status monitor in the response body as described in [Status and results of long-running operations](#status-and-results-of-long-running-operations). +:ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation. + +:ballot_box_with_check: **YOU SHOULD** include the `api-version` query parameter in the `Operation-Location` header with the same version passed on the initial request if it is required by the get operation on the status monitor. + +:ballot_box_with_check: **YOU SHOULD** allow any valid value of the `api-version` query parameter to be used in the get operation on the status monitor. #### PUT operation with additional long-running processing @@ -807,7 +811,7 @@ For a PUT (create or replace) with additional long-running processing: :white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. -:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header that matches an outstanding operation unless the request is identical to the prior request (a retry scenario). +:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header that matches an existing operation unless the request is identical to the prior request (a retry scenario). :white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. @@ -817,7 +821,11 @@ For a PUT (create or replace) with additional long-running processing: :white_check_mark: **DO** include response headers with any additional values needed for a GET request to the status monitor (e.g. location). -:ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation, but do not include an api-version query parameter. +:ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation. + +:ballot_box_with_check: **YOU SHOULD** include the `api-version` query parameter in the `Operation-Location` header with the same version passed on the initial request if it is required by the get operation on the status monitor. + +:ballot_box_with_check: **YOU SHOULD** allow any valid value of the `api-version` query parameter to be used in the get operation on the status monitor. #### Obtaining status and results of long-running operations From e4cc4b36be8ed21fbe7619a48d8128af64f55732 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 13 Jul 2022 08:29:12 -0700 Subject: [PATCH 429/729] Further improve guidance on api-version --- azure/ConsiderationsForServiceDesign.md | 2 +- azure/Guidelines.md | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 93665849..7693f97c 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -6,7 +6,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | -| 2022-Jun-08 | Update guidance on long-running operations | +| 2022-Jul-15 | Update guidance on long-running operations | | 2022-Feb-01 | Updated error guidance | | 2021-Sep-11 | Add long-running operations guidance | | 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 09c343d5..d3b2c0c9 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,12 +1,12 @@ # Microsoft Azure REST API Guidelines - + ## History | Date | Notes | | ----------- | -------------------------------------------------------------- | -| 2022-Jun-08 | Update guidance on long-running operations | +| 2022-Jul-15 | Update guidance on long-running operations | | 2022-May-11 | Drop guidance on version discovery | | 2022-Mar-29 | Add guidelines about using durations | | 2022-Mar-25 | Update guideline for date values in headers to follow RFC 7231 | @@ -698,7 +698,11 @@ Azure services need to change over time. However, when changing a service, there :white_check_mark: **DO** review any API changes with the Azure API Stewardship Board -:white_check_mark: **DO** use an `api-version` query parameter with a `YYYY-MM-DD` date value, with a `-preview` suffix for a preview service. +Clients specify the version of the API to be used in every request to the service, even requests to an `Operation-Location` or `nextLink` URL returned by the service. + +:white_check_mark: **DO** use a required query parameter named `api-version` on every operation for the client to specify the API version. + +:white_check_mark: **DO** use `YYYY-MM-DD` date values, with a `-preview` suffix for preview versions, as the valid values for `api-version`. ```text PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 From 0cdfbd0179c27de37c88bd30d5d65882ca1ad8fd Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 13 Jul 2022 12:25:40 -0400 Subject: [PATCH 430/729] added function examples --- graph/patterns/operations.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index 362f1766..5234a54e 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -45,8 +45,8 @@ You can consider related patterns such as [long running operations](./long-runni
    -``` - + + @@ -62,3 +62,4 @@ You can consider related patterns such as [long running operations](./long-runni +``` From 395a83b7ce7a262010e600f412368ceec2a9704f Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 3 Jul 2022 14:07:17 -0700 Subject: [PATCH 431/729] Add guidance on including api-version in nextLink --- azure/ConsiderationsForServiceDesign.md | 31 +++++++++++++++++++++++++ azure/Guidelines.md | 2 ++ 2 files changed, 33 insertions(+) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 7693f97c..215e5ea2 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -374,6 +374,37 @@ All responses should include the `x-ms-request-id` header with a unique id for t Finally, write sample code for your service's workflow and add the code you'd want customers using to gracefully recover from errors. Is it actually graceful? Is it something you'd be comfortable asking most customers to write? We also highly encourage reaching out to customers during private preview and asking them for code they've written against your service. Their error handling might match your expectations, you might find a strong need for better documentation, or you might find important opportunities to improve the errors you're returning. +## Pagination + +Operations that return a collection of resources may need should support pagination. +There are hard limits to the payload size of HTTP responses, and when the size of a collection or the resources themselves +can grow arbitrarily large there is the risk of exceeding this limit if the operation does not support pagination. +Further, adding support for pagination is a breaking change so it should be supported in the initial GA of the service +if there is any possibility that it will eventually be needed. + +There are two forms of pagination that MAY be supported by RESTful APIs. +Server-driven paging mitigates against denial-of-service attacks by forcibly paginating a request over multiple response payloads. +Client-driven paging enables clients to request only the number of resources that it can use at a given time. + +### Server-driven paging + +In server-driven paging, the service includes a `nextLink` property in the response to indicate that additional elements +exist in the collection. +The value of the `nextLink` property should be an opaque absolute URL that will return the next page of results. +The absence of a `nextLink` property means that no additional pages are available. +Since `nextLink` is an opaque URL it should include any query parameters required by the service, including `api-version`. + +The service determines how many items to include in the response and may choose a different number for different collections and even for different pages of the same collection. +An operation may allow the client to specify a maximum number of items in a response with an optional `maxpagesize` parameter. +Operations that support `maxpagesize` should return no more than the value specified in `maxpagesize` but may return fewer. + +### Client-driven paging + +An operation may support `skip` and `top` query parameters to allow the client to specify an offset into the collection +and the number of results to return, respectively. + +Note that when `top` specifies a value larger than the server-driven paging page size, the response will be paged accordingly. + ## Getting Help: The Azure REST API Stewardship Board The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index d3b2c0c9..8bc1d758 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -512,6 +512,8 @@ Note: To avoid potential collision of actions and resource ids, you should disal :white_check_mark: **DO** return a `nextLink` field with an absolute URL that the client can GET in order to retrieve the next page of the collection. +:white_check_mark: **DO** include any query parameters required by the service in `nextLink`, including `api-version`. + :ballot_box_with_check: **YOU SHOULD** use `value` as the name of the top-level array field unless a more appropriate name is available. :no_entry: **DO NOT** return the `nextLink` field at all when returning the last page of the collection. From a1edb310b2063a379dfd08db10015230c4bb88d1 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Tue, 5 Jul 2022 13:01:05 -0700 Subject: [PATCH 432/729] Apply suggestions from PR review Co-authored-by: Jeffrey Richter --- azure/ConsiderationsForServiceDesign.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 215e5ea2..656d161a 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -376,7 +376,7 @@ Finally, write sample code for your service's workflow and add the code you'd wa ## Pagination -Operations that return a collection of resources may need should support pagination. +Operations that return a collection of resources must consider pagination. There are hard limits to the payload size of HTTP responses, and when the size of a collection or the resources themselves can grow arbitrarily large there is the risk of exceeding this limit if the operation does not support pagination. Further, adding support for pagination is a breaking change so it should be supported in the initial GA of the service @@ -385,6 +385,7 @@ if there is any possibility that it will eventually be needed. There are two forms of pagination that MAY be supported by RESTful APIs. Server-driven paging mitigates against denial-of-service attacks by forcibly paginating a request over multiple response payloads. Client-driven paging enables clients to request only the number of resources that it can use at a given time. +Services should almost always support server-driven paging and may optionally support client-driven paging. ### Server-driven paging From 9398623333c3a506872b5ce8e7c9b2cf7f944bb8 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 6 Jul 2022 15:18:31 -0700 Subject: [PATCH 433/729] More updates from PR review --- azure/ConsiderationsForServiceDesign.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 656d161a..e887b05d 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -394,6 +394,8 @@ exist in the collection. The value of the `nextLink` property should be an opaque absolute URL that will return the next page of results. The absence of a `nextLink` property means that no additional pages are available. Since `nextLink` is an opaque URL it should include any query parameters required by the service, including `api-version`. +The service should honor a request to a URL derived from `nextLink` by replacing the value for the `apl-version` query parameter +with a different but valid api version. The service may reject the request if any other element of `nextLink` was modified. The service determines how many items to include in the response and may choose a different number for different collections and even for different pages of the same collection. An operation may allow the client to specify a maximum number of items in a response with an optional `maxpagesize` parameter. From 3928589dc67b001c9ff1f479fa5b9505a52a1892 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 15 Jul 2022 16:04:17 -0400 Subject: [PATCH 434/729] Updated text and added examples --- graph/patterns/navigation-property.md | 87 +++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 6 deletions(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 607f50d9..4be15095 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -7,12 +7,23 @@ Microsoft Graph API Design Pattern ## Problem -------- -Resources often contain information that identifies other related resources. Usually that information is contained in a returned representation as an id value. In order for a client to access the related resource it must request the primary resource, read the id value of the related resource and then construct a URL to the related resource using the Id value. This requires at least two round trips and requires the client know how to construct the URL to the related resource. +It is often valuable to represent a relationship between resources in an API. This may be a many-to-one or a one-to-many relationship. + +Relationships between resources are often implicitly represented by a property contained in one of the resources that identifies the other related resource. Usually that information returned in a representation as an id value and the property is named using a convention that identifies the target type of related resource. e.g. userId + +In many-to-one relationships, for a client to access the related resource it must request the primary resource, read the id value of the related resource and then construct a URL to the related resource using the Id value and knowledge of how the property name maps to the related resource. This requires at least two round trips and requires the client know how to construct the URL to the related resource. + +For both many-to-one and one-to-many relationships in order to retrieve information from both resources on both sides of the relationship requires at minimum two requests. + +Requiring two round trips to access this information is inefficient for some applications and the lack of a formally described relationship limits the ability for tooling to take advantage of the relationship to improve developer experience. ## Solution -------- -Navigation properties are an OData convention that allows an API designer to create a special kind of property in a model that references an related entity. In the HTTP API this property name translates to a path segment that can be appended to the URL of the primary resource in order to access a representation of the related resource. This prevents the client from needing to know any additional information on how to construct the URL to the related resource and the client does not need to retrieve the primary resource if it is only interested in the related resource. It is the responsibility of the API implementation to determine the Id of the related resource and return the representation of the related entity. +Navigation properties are an [OData convention](https://docs.microsoft.com/en-us/odata/webapi/model-builder-untyped#navigation-property) that allows an API designer to describe a special kind of property in a model that references an related entity. In the HTTP API this property name translates to a path segment that can be appended to the URL of the primary resource in order to access a representation of the related resource. This prevents the client from needing to know any additional information on how to construct the URL to the related resource and the client does not need to retrieve the primary resource if it is only interested in the related resource. It is the responsibility of the API implementation to determine the Id of the related resource and return the representation of the related entity. + +e.g. /user/{userId}/manager # many-to-one relationship + /user/{userId}/messages # one-to-many relationship Additionally, using the OData Expand query parameter, related entities can be transcluded into the primary entity so both can be retrieved in a single round trip. @@ -21,22 +32,86 @@ Additionally, using the OData Expand query parameter, related entities can be tr In the current Microsoft Graph implementation, support for navigation properties is limited to entities within the same backend service or the user entity.   +Navigation properties defined within an entity are not returned when retreiving the representation of an entity. + Implementing support for accessing the "$ref" of a navigation property allows a caller to return just the URL of related resource. e.g. `/user/23/manager/$ref`. This is useful when a client wishes to identity the related resource but doesn't need all of its properties. ## When to Use this Pattern ------------------------ -The use of navigation properties is preferred over +### "has a" relationships -  +The use of navigation properties is preferred over including an Id field to reference the related entity in a many-to-one relationship. Id values require a client to make two round trips to retrieve the details of a related entity. With a navigation property a client can retrieve a related entity in a single round trip. + +Navigation properties are also useful when clients sometimes want to retrieve both the primary entity and the related entity in a single round trip. The `expand` query parameter makes this possible. + +### "parent-child" relationships + +Resources that contain a parent Id property in a child resource can utilize a navigation property in the parent resource that is declared as a collection of child resources. If desirable, a parent navigation property can also be created in the child resource to the parent resource. This is usually not necessary as the parent URL is a subset of child resource URL. The main use of this would be when retrieving child resources and choosing to expand properties of the parent resource so that both can be retrieved in a single request. + +`/invoice/{invoiceId}/items/{itemId}?expand=parentInvoice(select=invoiceDate,Customer)` + +One other use case is when child resources appear in a non-contained collection and there is a desire to access the canonical parent: + +`/me/pinnedChannels/{channelId}/team` ## Example ------- -*Provide a short example from real life* +### Retrieving a related entity -  +```http +GET /users/{id}/manager +``` + +### Retrieving a reference to a related entity + +```http +GET /users/{id}/manager/$ref +``` + +### Retrieving an entity with a related entity included + +```http +GET /users/{id}?expand=manager +``` +### Creating an entity with a reference to a related entity + +```http +POST /users/{id} +Content-Type: application/json + +{ + "displayName": "Bob", + "manager@bind": "https://graph.microsoft.com/v1.0/users/{someGuid}" +} +``` + +### Updating a related entity reference   +```http +PATCH /users/{id} +Content-Type: application/json +{ + "displayName": "Bob", + "manager@bind": "https://graph.microsoft.com/v1.0/users/{someGuid}" +} +``` + +  +```http +PUT /users/{id}/manager/$ref +Content-Type: application/json + +{ + "@OData.Id": "https://graph.microsoft.com/v1.0/users/{someGuid}" +} +``` +  +### Clear a related entity reference   +```http +DELETE /users/{id}/manager/$ref +``` From 0a5ad5e9791aa90a3203190e985fcaeb61412b28 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 15 Jul 2022 17:02:13 -0400 Subject: [PATCH 435/729] Update examples with response info --- graph/patterns/navigation-property.md | 78 +++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 4be15095..b42f4259 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -62,33 +62,84 @@ One other use case is when child resources appear in a non-contained collection ```http GET /users/{id}/manager + +200 OK +Content-Type: application/json + +{ + "@odata.type": "#microsoft.graph.user", + "id": "6b3ee805-c449-46a8-aac8-8ff9cff5d213", + "displayName": "Bob Boyce" +} ``` ### Retrieving a reference to a related entity ```http GET /users/{id}/manager/$ref + +200 OK +Content-Type: application/json + +{ + "@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/6b3ee805-c449-46a8-aac8-8ff9cff5d213/Microsoft.DirectoryServices.User" +} ``` +Note: Currently the base URL returned in $ref results are incorrect. In order to process these URLs the client will need to convert the URL to a Graph URL. ### Retrieving an entity with a related entity included ```http -GET /users/{id}?expand=manager +GET /users/{id}?select=id,displayName&expand=manager(select=id,displayName) + +200 OK +Content-Type: application/json + +{ + "id": "3f057904-f936-4bf0-9fcc-c1e6f84289d8", + "displayName": "Jim James", + "manager": { + "@odata.type": "#microsoft.graph.user", + "id": "6b3ee805-c449-46a8-aac8-8ff9cff5d213", + "displayName": "Bob Boyce" + } +} ``` ### Creating an entity with a reference to a related entity +Create a new user that references an existing manager ```http POST /users/{id} Content-Type: application/json { "displayName": "Bob", - "manager@bind": "https://graph.microsoft.com/v1.0/users/{someGuid}" + "manager@bind": "https://graph.microsoft.com/v1.0/users/{managerId}" +} + +201 Created +``` + +Create a new user and the users manager and create a relationship between the two. + +```http +POST /users/{id} +Content-Type: application/json + +{ + "displayName": "Jim James", + "manager": { + "displayName": "Bob Boyce" + } } + +201 Created ``` ### Updating a related entity reference + +Update the user entity to contain a relationship to an existing manager.   ```http PATCH /users/{id} @@ -96,22 +147,39 @@ Content-Type: application/json { "displayName": "Bob", - "manager@bind": "https://graph.microsoft.com/v1.0/users/{someGuid}" + "manager@bind": "https://graph.microsoft.com/v1.0/users/{managerId}" } + +204 No Content ``` -  +Create a relationship between the user and the existing manager. + ```http PUT /users/{id}/manager/$ref Content-Type: application/json { - "@OData.Id": "https://graph.microsoft.com/v1.0/users/{someGuid}" + "@OData.Id": "https://graph.microsoft.com/v1.0/users/{managerId}" } + +204 No Content ```   ### Clear a related entity reference + +Remove the relationship between the user and the manager.   ```http DELETE /users/{id}/manager/$ref + +204 No Content +``` + +Delete the related entity. + +```http +DELETE /users/{id}/manager + +204 No Content ``` From 90ba65e8ede00d05082d1b7a50d8131ea91b191b Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 15 Jul 2022 17:06:57 -0400 Subject: [PATCH 436/729] adding precision --- graph/patterns/navigation-property.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index b42f4259..1a10e341 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -2,7 +2,7 @@ Microsoft Graph API Design Pattern -*A navigation property is used to identify a relationship between two resources.* +*A navigation property is used to identify a relationship between resources.* ## Problem -------- From 41bf3d38471da53417d4c297a6a04f124276d188 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin Date: Fri, 22 Jul 2022 10:42:56 -0700 Subject: [PATCH 437/729] updating alternate key syntax to be in line with what AGS supports --- graph/patterns/alternate-key.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graph/patterns/alternate-key.md b/graph/patterns/alternate-key.md index 5bec7466..2ca507fd 100644 --- a/graph/patterns/alternate-key.md +++ b/graph/patterns/alternate-key.md @@ -63,21 +63,21 @@ Declare `mail` and `ssn` as alternate keys on an entity: - + - + - + - + - + From 809661504e2c83ecd5383ac2c9e92fb472353dfe Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 10 Aug 2022 13:04:48 -0400 Subject: [PATCH 438/729] problem update --- graph/patterns/operations.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index 5234a54e..bc06096b 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -2,16 +2,16 @@ Microsoft Graph API Design Pattern -*The operations pattern provides the ability to model a change that impacts multiple resources and can't be effectively modeled by using HTTP methods.* +*The operations pattern provides the ability to model a change that might impact multiple resources and can't be effectively modeled by using HTTP methods.* ## Problem -Sometimes when modeling a complex business domain, API designers need to model a business operation that effects multiple resources and needs to be performed as a single unit. Modeling the operation via HTTP methods on each individual resource might be either ineffective or not reflect how it's processed by the backend service. In addition, the operation might produce observable side effects. +Sometimes when modeling a complex business domain, API designers need to model a business operation that effects one or multiple resources and has additional semantic meaning that cannot be expressed by HTTP methods. Modeling the operation via HTTP methods on each individual resource might be either ineffective or expose internal implementation details. In addition, the operation might produce observable side effects. ## Solution -To address these use cases, API designers might use operational resources such as functions or actions. -If the operation doesn't have any side effects and MUST return a single instance of a type or a collection of instances, then the designer SHOULD use the OData function; otherwise, the designer can model the operation as an action. +To address these use cases, API designers can use operational resources such as functions or actions. +If the operation doesn't have any side effects and MUST return a single instance of a type or a collection of instances, then the designer SHOULD use OData functions; otherwise, the designer can model the operation as an action. ## When to use this pattern From c195dd4b135e72cb7dd964988e14d604b9dcd2dc Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 16 Aug 2022 20:13:41 -0400 Subject: [PATCH 439/729] added method for functions --- graph/patterns/operations.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index bc06096b..5a94edf2 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -21,7 +21,8 @@ You can consider related patterns such as [long running operations](./long-runni ## Issues and considerations -- Microsoft Graph does NOT support unbound actions or functions. Bound actions and functions are invoked on resources matching the type of the binding parameter. The binding parameter can be of any type, and it MAY be Nullable. For Microsoft Graph, actions and functions must have the `isBound="true"` attribute. The first parameter is the binding parameter. +- Microsoft Graph does NOT support unbound actions or functions. Bound actions and functions are invoked on resources matching the type of the binding parameter. The binding parameter can be of any type, and parameter value MAY be Nullable. +For Microsoft Graph, actions and functions must have the `isBound="true"` attribute. The first parameter is the binding parameter. - Both actions and functions support overloading, meaning a schema might contain multiple actions or functions with the same name. The overload rules as per the OData [standard](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionOverloads) apply when adding parameters to actions and functions. @@ -33,33 +34,34 @@ You can consider related patterns such as [long running operations](./long-runni - Microsoft Graph supports the use of optional parameters. The optional parameter annotation can be used instead of creating function or action overloads when unnecessary. -- API designer **MUST** use POST to call operations on resources. +- API designer **MUST** use POST to call actions on resources. +- API designer **MUST** use GET to call functions on resources. - The addition of a new mandatory not-nullable parameter to an existing action or function is a breaking change and is not allowed without proper versioning. ## Example ``` - + - + - + - + - + - + ``` From 8381acec590accd50ac1b64decaf0b004d255bc1 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 17 Aug 2022 20:34:20 -0400 Subject: [PATCH 440/729] Update graph/patterns/operations.md Co-authored-by: Garrett DeBruin --- graph/patterns/operations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index 5a94edf2..3031358c 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -11,7 +11,7 @@ Sometimes when modeling a complex business domain, API designers need to model a ## Solution To address these use cases, API designers can use operational resources such as functions or actions. -If the operation doesn't have any side effects and MUST return a single instance of a type or a collection of instances, then the designer SHOULD use OData functions; otherwise, the designer can model the operation as an action. +If the operation doesn't have any side effects and MUST return a single instance of a type or a collection of instances, then the designer MUST use OData functions; otherwise, the designer can model the operation as an action. ## When to use this pattern From f9d769e1eae50de68ce3dc4d7b7cb543ec673891 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 17 Aug 2022 20:35:04 -0400 Subject: [PATCH 441/729] Update graph/patterns/operations.md Co-authored-by: Garrett DeBruin --- graph/patterns/operations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index 3031358c..42a4714d 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -37,7 +37,7 @@ For Microsoft Graph, actions and functions must have the `isBound="true"` attrib - API designer **MUST** use POST to call actions on resources. - API designer **MUST** use GET to call functions on resources. -- The addition of a new mandatory not-nullable parameter to an existing action or function is a breaking change and is not allowed without proper versioning. +- The addition of a new mandatory not-nullable parameter to an existing action or function is a breaking change and is not allowed without proper versioning that is in accordance with our [deprecation guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/graph/deprecation.md). ## Example From 33b109b29f975d5c0b7733ebee41ed65fea3dcb0 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 17 Aug 2022 20:38:08 -0400 Subject: [PATCH 442/729] Update operations.md --- graph/patterns/operations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index 42a4714d..43ea823a 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -6,7 +6,7 @@ Microsoft Graph API Design Pattern ## Problem -Sometimes when modeling a complex business domain, API designers need to model a business operation that effects one or multiple resources and has additional semantic meaning that cannot be expressed by HTTP methods. Modeling the operation via HTTP methods on each individual resource might be either ineffective or expose internal implementation details. In addition, the operation might produce observable side effects. +Sometimes when modeling a complex business domain, API designers need to model a business operation that effects one or multiple resources and has additional semantic meaning that cannot be expressed by HTTP methods. Modeling the operation via HTTP methods on each individual resource might be either inefficient or expose internal implementation details. In addition, the operation might produce observable side effects. ## Solution From 42e4a13da1b065d09c363077dd6f4c0a989d9d65 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Thu, 18 Aug 2022 10:35:25 -0700 Subject: [PATCH 443/729] Guidelines for 'azure-deprecating' response header --- azure/Guidelines.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 8bc1d758..056cf9b4 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -6,6 +6,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2022-Aug-20 | Add "azure-deprecating" response header | | 2022-Jul-15 | Update guidance on long-running operations | | 2022-May-11 | Drop guidance on version discovery | | 2022-Mar-29 | Add guidelines about using durations | @@ -191,6 +192,7 @@ _x-ms-request-id_ | Response | 4227cdc5-9f48-4e84-921a-10967cb785a0 ETag | Response | "67ab43" (see [Conditional Requests](#Conditional-Requests)) last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT _x-ms-error-code_ | Response | (see [Handling Errors](#Handling-Errors)) +_azure-deprecating_ | Response | (see [Deprecating Behavior](#Deprecating-Behavior)) retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)) :white_check_mark: **DO** support all headers shown in _italics_ @@ -747,6 +749,20 @@ While removing a value from an enum is a breaking change, adding value to an enu > :ballot_box_with_check: **You SHOULD** use extensible enums unless you are positive that the symbol set will **NEVER** change over time. +### Deprecating Behavior Notification + +When the [API Versioning](#API-Versioning) guidance above cannot be followed and the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) approve a breaking change to the operation/service, the operation/service that is being deprecated must add the `azure-deprecating` response header with a semicolon-delimited string notifying the caller what is being deprecated, when it will no longer function, and a URL linking to more information. The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. + +:white_check_mark: **DO** add the 'azure-deprercating' header with a string value to all service operations in your service's contract file (cadl/swagger). + +:white_check_mark: **DO** include this header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. NOTE: We do not want to scare customers with this header. + +:white_check_mark: **DO** make the header's value a semicolon-delimited string indicating a set of deprecations where each one indicates what is deprecating, when it is deprecating, and a URL to more information. For example: + +```text +azure-deprecating: api-version=2009-27-07 will stop working on 2022-12-01 (https://azure.microsoft.com/en-us/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will stop working on 2020-10-30 (https://azure.microsoft.com/en-us/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/) +``` + ### Repeatability of requests The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. From cc27a86f46e8cc43381f1c7b339b5a8d192d4d53 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Sun, 21 Aug 2022 11:02:25 -0400 Subject: [PATCH 444/729] Added considerations around deprectation --- azure/ConsiderationsForServiceDesign.md | 35 +++++++++++++++++-------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index e887b05d..a795eadb 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -77,6 +77,13 @@ Understanding how your service is used and defining its model and interaction pa :white_check_mark: **DO** create an [OpenAPI Definition](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. +## Design for Change Resiliency +As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. + +:ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. + +:ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. + ## Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. @@ -90,6 +97,13 @@ Before releasing your API plan to invest significant design effort, get customer :ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. +## Communicate Deprecations +As your service evolves over time, it will be natural that you want to remove operations that are no longer needed. For example, additioanl requirements or new capability in your service, may have resulted in a new operation that, effectively, replaces an old one. +Azure has a well established breaking changes policy that describes how to approach these kinds of changes. As part of this policy, the service team is required to clearly communicate to customers when their API is changing, e.g. deprecating operations. Often, this is done via an email to the address that is attached to the Azure subscription. + +However, given how many organizations are structured, it's common that this email address is different from the actual people writing code against your API. To address this, the service API should declare that it may return the `azure-deprecating` header, to indicate that this operation will be removed in the future. There is a simple string convention, specied in the [Azure REST API Guidelines](https://aka.ms/azapi/guidelines) that provides more information about the forthcoming deprecation. +This header is targeted at developers or operation professionals, and it is intended to give them enough information and lead time to properly adapt to this change. Your documentation should reference this header and encourage logging and alerting practices based on its presence. + ## Avoid Surprises A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. @@ -116,13 +130,6 @@ Another important design pattern for avoiding surprises is idempotency. An opera HTTP requires certain operations like GET, PUT, and DELETE to be idempotent, but for cloud services it is important to make _all_ operations idempotent so that clients can use retry in failure scenarios without risk of unintended consequences. See the [HTTP Request / Response Pattern section of the Guidelines](./Guidelines.md#http-request--response-pattern) for detailed guidance on making operations idempotent. -## Design for Change Resiliency -As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. - -:ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. - -:ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. - ## Action Operations Most operations conform to one of the standard REST Create, Read, Update, Delete, or List (CRUDL) style of operations. We refer to all other operations as "action" operations. Some examples of action operations are to reboot a VM, or send an email. @@ -362,17 +369,23 @@ You should use appropriate [HTTP status codes](https://developer.mozilla.org/doc - A `404` status code tells them the blob doesn't exist and the customer can report the error to their users - A `BlobNotFound` or `ContainerNotFound` error code will tell them why the blob doesn't exist so they can take steps to recreate it -The [common error schema in the Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors) allows nested details and inner errors that have their own error codes, but the top-level error code is the most important. The HTTP status code and the top-level error code are the only part of your error that we consider part of your API contract that follows the same compatibility requirements as the rest of your API. Importantly, this means you **changing the HTTP status code or top-level error code for an API is a breaking change**. You can only return new status codes and error codes in future API versions if customers make use of new features that trigger new classes of errors. Battle tested error handling is some of the hardest code to get right and we can't break that for customers when they upgrade to the latest version. The rest of the properties in your error like `message`, `details`, etc., are not considered part of your API contract and can change to improve the diagnosability of your service. +The [common error schema in the Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors) allows nested details and inner errors that have their own error codes, but the top-level error code is the most important. The HTTP status code and the top-level error code are the only part of your error that we consider part of your API contract that follows the same compatibility requirements as the rest of your API. +Importantly, this means you **changing the HTTP status code or top-level error code for an API is a breaking change**. +You can only return new status codes and error codes in future API versions if customers make use of new features that trigger new classes of errors. Battle tested error handling is some of the hardest code to get right and we can't break that for customers when they upgrade to the latest version. The rest of the properties in your error like `message`, `details`, etc., are not considered part of your API contract and can change to improve the diagnosability of your service. -You should also return the top-level error code as the `x-ms-error-code` response header so client libraries have the ability to automatically retry requests when possible without having to parse a JSON payload. We recommend unique error codes like `ContainerBeingDeleted` for every distinct recoverable error that can occur, but suggest reusing common error codes like `InvalidHeaderValue` for usage errors where a descriptive error message is more important for resolving the problem. The Storage [Common](https://docs.microsoft.com/rest/api/storageservices/common-rest-api-error-codes) and [Blob](https://docs.microsoft.com/rest/api/storageservices/blob-service-error-codes) error codes are a good starting point if you're looking for examples. You can [define an enum in your spec](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/storage/data-plane/Microsoft.BlobStorage/preview/2021-04-10/blob.json#L10419) with `"modelAsString": true` that lists all of the top-level error codes to make it [easier for your customers to handle specific error codes](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/storage/Azure.Storage.Blobs#troubleshooting). +You should also return the top-level error code as the `x-ms-error-code` response header so client libraries have the ability to automatically retry requests when possible without having to parse a JSON payload. We recommend unique error codes like `ContainerBeingDeleted` for every distinct recoverable error that can occur, but suggest reusing common error codes like `InvalidHeaderValue` for usage errors where a descriptive error message is more important for resolving the problem. +The Storage [Common](https://docs.microsoft.com/rest/api/storageservices/common-rest-api-error-codes) and [Blob](https://docs.microsoft.com/rest/api/storageservices/blob-service-error-codes) error codes are a good starting point if you're looking for examples. +You can [define an enum in your spec](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/storage/data-plane/Microsoft.BlobStorage/preview/2021-04-10/blob.json#L10419) with `"modelAsString": true` that lists all of the top-level error codes to make it [easier for your customers to handle specific error codes](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/storage/Azure.Storage.Blobs#troubleshooting). You should not document specific error status codes in your OpenAPI/Swagger spec. The `"default"` response is the only thing AutoRest considers an error response unless you provide other annotations. Every unique status code turns into a separate code path in your client libraries so we do not encourage this practice. The only reason to document specific error status codes is if they return a different error response than the default, but that is also heavily discouraged. -Be as precise as possible when writing error messages. A message with just `Invalid Argument` is almost useless to a customer who sent 100KB of JSON to your endpoint. ``Query parameter `top` must be less than or equal to 1000`` tells a customer exactly what went wrong so they can quickly fix the problem. Don't go overboard while writing great, understandable error messages and include any sensitive customer information or secrets though. Many developers will blindly write any error to logs that don't have the same level of access control as Azure resources. +Be as precise as possible when writing error messages. A message with just `Invalid Argument` is almost useless to a customer who sent 100KB of JSON to your endpoint. ``Query parameter `top` must be less than or equal to 1000`` tells a customer exactly what went wrong so they can quickly fix the problem. +Don't go overboard while writing great, understandable error messages and include any sensitive customer information or secrets though. Many developers will blindly write any error to logs that don't have the same level of access control as Azure resources. All responses should include the `x-ms-request-id` header with a unique id for the request, but this is particularly important for error responses. Service logs for the request should contain the `x-ms-request-id` so that support staff can use this value to diagnose specific customer reported errors. -Finally, write sample code for your service's workflow and add the code you'd want customers using to gracefully recover from errors. Is it actually graceful? Is it something you'd be comfortable asking most customers to write? We also highly encourage reaching out to customers during private preview and asking them for code they've written against your service. Their error handling might match your expectations, you might find a strong need for better documentation, or you might find important opportunities to improve the errors you're returning. +Finally, write sample code for your service's workflow and add the code you'd want customers using to gracefully recover from errors. Is it actually graceful? Is it something you'd be comfortable asking most customers to write? +We also highly encourage reaching out to customers during private preview and asking them for code they've written against your service. Their error handling might match your expectations, you might find a strong need for better documentation, or you might find important opportunities to improve the errors you're returning. ## Pagination From 422bacf714faaae34d6014bbd2d150e198871baa Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Mon, 22 Aug 2022 15:09:49 -0700 Subject: [PATCH 445/729] Update azure/Guidelines.md Co-authored-by: Tom Kerkhove --- azure/Guidelines.md | 1 + 1 file changed, 1 insertion(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 056cf9b4..92e376b3 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -763,6 +763,7 @@ When the [API Versioning](#API-Versioning) guidance above cannot be followed and azure-deprecating: api-version=2009-27-07 will stop working on 2022-12-01 (https://azure.microsoft.com/en-us/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will stop working on 2020-10-30 (https://azure.microsoft.com/en-us/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/) ``` +:no_entry: **DO NOT** introduce this header without this pattern without approval from [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) and an official deprecation notice on [Azure Updates](https://azure.microsoft.com/updates/). ### Repeatability of requests The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. From 5e6c7908b45f885b01fbced5c05a5b19d0e81c10 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Mon, 22 Aug 2022 15:18:47 -0700 Subject: [PATCH 446/729] Update azure/Guidelines.md Co-authored-by: Tom Kerkhove --- azure/Guidelines.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 92e376b3..49e25e9b 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -757,10 +757,22 @@ When the [API Versioning](#API-Versioning) guidance above cannot be followed and :white_check_mark: **DO** include this header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. NOTE: We do not want to scare customers with this header. -:white_check_mark: **DO** make the header's value a semicolon-delimited string indicating a set of deprecations where each one indicates what is deprecating, when it is deprecating, and a URL to more information. For example: +:white_check_mark: **DO** make the header's value a semicolon-delimited string indicating a set of deprecations where each one indicates what is deprecating, when it is deprecating, and a URL to more information. +Deprecations should use the following pattern: ```text -azure-deprecating: api-version=2009-27-07 will stop working on 2022-12-01 (https://azure.microsoft.com/en-us/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will stop working on 2020-10-30 (https://azure.microsoft.com/en-us/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/) + will retire on (`url`) +``` + +Where the following placeholders should be provided: +- `description`: a human-readable description of what is being deprecated +- `date`: the target date that this will be deprecated. This should be expressed following the format in [ISO 8601](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "2022-10-31". +- `url`: a fully qualified url that the user can follow to learn more about what is being deprecated, preferably to Azure Updates. + +For example: +```text +azure-deprecating: API version 2009-27-07 will retire on 2022-12-01 (https://azure.microsoft.com/en-us/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will retire on 2020-10-30 (https://azure.microsoft.com/en-us/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/) +``` ``` :no_entry: **DO NOT** introduce this header without this pattern without approval from [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) and an official deprecation notice on [Azure Updates](https://azure.microsoft.com/updates/). From 5eb4caaf6c00ac4a73a14686c12d3c7f46a46b4b Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Mon, 22 Aug 2022 15:26:03 -0700 Subject: [PATCH 447/729] Update azure/Guidelines.md Co-authored-by: Tom Kerkhove --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 49e25e9b..91b8a13c 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -753,7 +753,7 @@ While removing a value from an enum is a breaking change, adding value to an enu When the [API Versioning](#API-Versioning) guidance above cannot be followed and the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) approve a breaking change to the operation/service, the operation/service that is being deprecated must add the `azure-deprecating` response header with a semicolon-delimited string notifying the caller what is being deprecated, when it will no longer function, and a URL linking to more information. The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. -:white_check_mark: **DO** add the 'azure-deprercating' header with a string value to all service operations in your service's contract file (cadl/swagger). +:white_check_mark: **DO** add the 'azure-deprecating' header with a string value to all service operations in your service's contract file (cadl/swagger). :white_check_mark: **DO** include this header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. NOTE: We do not want to scare customers with this header. From 69ae7ebac35c67df18557b10e7118c31c9db899a Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Tue, 23 Aug 2022 08:28:30 -0700 Subject: [PATCH 448/729] Update azure/Guidelines.md Co-authored-by: Tom Kerkhove --- azure/Guidelines.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 91b8a13c..33fd6d67 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -751,7 +751,9 @@ While removing a value from an enum is a breaking change, adding value to an enu ### Deprecating Behavior Notification -When the [API Versioning](#API-Versioning) guidance above cannot be followed and the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) approve a breaking change to the operation/service, the operation/service that is being deprecated must add the `azure-deprecating` response header with a semicolon-delimited string notifying the caller what is being deprecated, when it will no longer function, and a URL linking to more information. The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. +When the [API Versioning](#API-Versioning) guidance above cannot be followed and the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) approve a [breaking change](#123-definition-of-a-breaking-change) to a specific API version it must be communicated to its callers. The API version that is being deprecated must add the `azure-deprecating` response header with a semicolon-delimited string notifying the caller what is being deprecated, when it will no longer function, and a URL linking to more information such as what new operation they should use instead. + +The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation and use a newer API version or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. :white_check_mark: **DO** add the 'azure-deprecating' header with a string value to all service operations in your service's contract file (cadl/swagger). From 68b68edd799a7cc7a61b71aae385875c00463009 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Tue, 23 Aug 2022 08:28:44 -0700 Subject: [PATCH 449/729] Update azure/Guidelines.md Co-authored-by: Heath Stewart --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 33fd6d67..0dddfc59 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -773,7 +773,7 @@ Where the following placeholders should be provided: For example: ```text -azure-deprecating: API version 2009-27-07 will retire on 2022-12-01 (https://azure.microsoft.com/en-us/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will retire on 2020-10-30 (https://azure.microsoft.com/en-us/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/) +azure-deprecating: API version 2009-27-07 will retire on 2022-12-01 (https://azure.microsoft.com/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will retire on 2020-10-30 (https://azure.microsoft.com/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/) ``` ``` From 431a9162cae430bfd0d5b3a2c204a21740c45c3b Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Tue, 23 Aug 2022 08:28:58 -0700 Subject: [PATCH 450/729] Update azure/Guidelines.md Co-authored-by: Tom Kerkhove --- azure/Guidelines.md | 1 - 1 file changed, 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 0dddfc59..981ef0f7 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -775,7 +775,6 @@ For example: ```text azure-deprecating: API version 2009-27-07 will retire on 2022-12-01 (https://azure.microsoft.com/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will retire on 2020-10-30 (https://azure.microsoft.com/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/) ``` -``` :no_entry: **DO NOT** introduce this header without this pattern without approval from [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) and an official deprecation notice on [Azure Updates](https://azure.microsoft.com/updates/). ### Repeatability of requests From b618b70a5b5c60e18adb4c8bf8ac1cbaee3979b2 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 23 Aug 2022 12:25:36 -0400 Subject: [PATCH 451/729] Update azure/ConsiderationsForServiceDesign.md Co-authored-by: Tom Kerkhove --- azure/ConsiderationsForServiceDesign.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index a795eadb..268eeed9 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -98,7 +98,7 @@ Before releasing your API plan to invest significant design effort, get customer :ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. ## Communicate Deprecations -As your service evolves over time, it will be natural that you want to remove operations that are no longer needed. For example, additioanl requirements or new capability in your service, may have resulted in a new operation that, effectively, replaces an old one. +As your service evolves over time, it will be natural that you want to remove operations that are no longer needed. For example, additional requirements or new capability in your service, may have resulted in a new operation that, effectively, replaces an old one. Azure has a well established breaking changes policy that describes how to approach these kinds of changes. As part of this policy, the service team is required to clearly communicate to customers when their API is changing, e.g. deprecating operations. Often, this is done via an email to the address that is attached to the Azure subscription. However, given how many organizations are structured, it's common that this email address is different from the actual people writing code against your API. To address this, the service API should declare that it may return the `azure-deprecating` header, to indicate that this operation will be removed in the future. There is a simple string convention, specied in the [Azure REST API Guidelines](https://aka.ms/azapi/guidelines) that provides more information about the forthcoming deprecation. From ff079f61161b647d1ad86bf47a34d7d12d1d697b Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 24 Aug 2022 11:32:28 -0400 Subject: [PATCH 452/729] additional conditions --- graph/patterns/operations.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index 43ea823a..4a7dbf6e 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -17,6 +17,13 @@ If the operation doesn't have any side effects and MUST return a single instance The operations pattern is well suited to use cases that cannot be modeled as a single HTTP method on a resource and require either multiple round trips to complete a single logical operation or produce one or multiple side effects. +The operation pattern might be justified when a modeling operation represents one or combination of the following: + +- a change of a resource (i.e., increment the value of a property) rather than a state (i.e., the final value of the property) +- complex processing logic that shouldn't be exposed to the client +- operation parameters might convey a restricted set of option (i.e., a report that can has to specify a date range) +- the operation leverage some service-side data not exposed to (or easily retrieved in context by) the user. + You can consider related patterns such as [long running operations](./long-running-operations.md) and [change tracking](./change-tracking.md). ## Issues and considerations From 3a91ca41f14f3809a846c4c531b694476cd80383 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Wed, 24 Aug 2022 09:57:46 -0700 Subject: [PATCH 453/729] Update azure/Guidelines.md Co-authored-by: tg-msft --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 981ef0f7..55f3f428 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -763,7 +763,7 @@ The purpose is to inform customers (when debugging/logging responses) that they Deprecations should use the following pattern: ```text - will retire on (`url`) + will retire on (`url`); ``` Where the following placeholders should be provided: From bc5cb85d01a37145f2132d6279f259496499c3ff Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Wed, 24 Aug 2022 10:01:54 -0700 Subject: [PATCH 454/729] Update azure/Guidelines.md --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 55f3f428..8352779a 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -753,7 +753,7 @@ While removing a value from an enum is a breaking change, adding value to an enu When the [API Versioning](#API-Versioning) guidance above cannot be followed and the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) approve a [breaking change](#123-definition-of-a-breaking-change) to a specific API version it must be communicated to its callers. The API version that is being deprecated must add the `azure-deprecating` response header with a semicolon-delimited string notifying the caller what is being deprecated, when it will no longer function, and a URL linking to more information such as what new operation they should use instead. -The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation and use a newer API version or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. +The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation and use a newer API version or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. The string is _not_ part of an API contract (except for the semi-colon delimiters) and may be changed/improved at any time without incurring a breaking change. :white_check_mark: **DO** add the 'azure-deprecating' header with a string value to all service operations in your service's contract file (cadl/swagger). From 62ee6d924034a0901692286b275544e5a0d0fd9d Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Wed, 24 Aug 2022 10:02:41 -0700 Subject: [PATCH 455/729] Update azure/Guidelines.md Co-authored-by: tg-msft --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 8352779a..41403f5d 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -776,7 +776,7 @@ For example: azure-deprecating: API version 2009-27-07 will retire on 2022-12-01 (https://azure.microsoft.com/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will retire on 2020-10-30 (https://azure.microsoft.com/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/) ``` -:no_entry: **DO NOT** introduce this header without this pattern without approval from [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) and an official deprecation notice on [Azure Updates](https://azure.microsoft.com/updates/). +:no_entry: **DO NOT** introduce this header without approval from [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) and an official deprecation notice on [Azure Updates](https://azure.microsoft.com/updates/). ### Repeatability of requests The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. From fcb0bf40a36ae013b97648633a14eb239850ca12 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 26 Aug 2022 15:06:01 -0400 Subject: [PATCH 456/729] Update graph/patterns/navigation-property.md Co-authored-by: Dan Kershaw [MSFT] --- graph/patterns/navigation-property.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 1a10e341..1060dae4 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -160,7 +160,7 @@ PUT /users/{id}/manager/$ref Content-Type: application/json { - "@OData.Id": "https://graph.microsoft.com/v1.0/users/{managerId}" + "@odata.id": "https://graph.microsoft.com/v1.0/users/{managerId}" } 204 No Content From 7a17ba851f2f8cc5cf098d9e797b20a62a76e636 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 26 Aug 2022 16:57:51 -0400 Subject: [PATCH 457/729] Updated based on comments --- graph/patterns/navigation-property.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 1a10e341..b9a37e10 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -9,13 +9,9 @@ Microsoft Graph API Design Pattern It is often valuable to represent a relationship between resources in an API. This may be a many-to-one or a one-to-many relationship. -Relationships between resources are often implicitly represented by a property contained in one of the resources that identifies the other related resource. Usually that information returned in a representation as an id value and the property is named using a convention that identifies the target type of related resource. e.g. userId +Relationships between resources are often implicitly represented by a property contained in one of the resources that provides a key to a related resource. Usually that information is returned in a representation as an id value and the property is named using a convention that identifies the target type of related resource. e.g. userId -In many-to-one relationships, for a client to access the related resource it must request the primary resource, read the id value of the related resource and then construct a URL to the related resource using the Id value and knowledge of how the property name maps to the related resource. This requires at least two round trips and requires the client know how to construct the URL to the related resource. - -For both many-to-one and one-to-many relationships in order to retrieve information from both resources on both sides of the relationship requires at minimum two requests. - -Requiring two round trips to access this information is inefficient for some applications and the lack of a formally described relationship limits the ability for tooling to take advantage of the relationship to improve developer experience. +The use of foreign key properties to describe related resources is a weakly typed mechanism and requires additional information for a developer to traverse the relationship. Discovery of related resources is not trivial. ## Solution -------- @@ -32,7 +28,7 @@ Additionally, using the OData Expand query parameter, related entities can be tr In the current Microsoft Graph implementation, support for navigation properties is limited to entities within the same backend service or the user entity.   -Navigation properties defined within an entity are not returned when retreiving the representation of an entity. +Navigation properties defined within an entity are not returned when retreiving the representation of an entity unless explicity desired by a service. Implementing support for accessing the "$ref" of a navigation property allows a caller to return just the URL of related resource. e.g. `/user/23/manager/$ref`. This is useful when a client wishes to identity the related resource but doesn't need all of its properties. @@ -51,6 +47,8 @@ Resources that contain a parent Id property in a child resource can utilize a na `/invoice/{invoiceId}/items/{itemId}?expand=parentInvoice(select=invoiceDate,Customer)` +### "child-parent" relationships + One other use case is when child resources appear in a non-contained collection and there is a desire to access the canonical parent: `/me/pinnedChannels/{channelId}/team` @@ -73,6 +71,13 @@ Content-Type: application/json } ``` +This navigation property could be described with the following CSDL: +```xml + + + +``` + ### Retrieving a reference to a related entity ```http From 100d916491b619d9d7a34a7bd2206dd8eeb7011f Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 26 Aug 2022 17:09:30 -0400 Subject: [PATCH 458/729] Fixed comment about Graph limitations --- graph/patterns/navigation-property.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index f2a3f8e5..e2d1e0de 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -16,7 +16,7 @@ The use of foreign key properties to describe related resources is a weakly type ## Solution -------- -Navigation properties are an [OData convention](https://docs.microsoft.com/en-us/odata/webapi/model-builder-untyped#navigation-property) that allows an API designer to describe a special kind of property in a model that references an related entity. In the HTTP API this property name translates to a path segment that can be appended to the URL of the primary resource in order to access a representation of the related resource. This prevents the client from needing to know any additional information on how to construct the URL to the related resource and the client does not need to retrieve the primary resource if it is only interested in the related resource. It is the responsibility of the API implementation to determine the Id of the related resource and return the representation of the related entity. +Navigation properties are an [OData convention](https://docs.microsoft.com/en-us/odata/webapi/model-builder-untyped#navigation-property) defined in the [CSDL Specification](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#_Toc38530365) that allows an API designer to describe a special kind of property in a model that references an related entity. In the HTTP API this property name translates to a path segment that can be appended to the URL of the primary resource in order to access a representation of the related resource. This prevents the client from needing to know any additional information on how to construct the URL to the related resource and the client does not need to retrieve the primary resource if it is only interested in the related resource. It is the responsibility of the API implementation to determine the Id of the related resource and return the representation of the related entity. e.g. /user/{userId}/manager # many-to-one relationship /user/{userId}/messages # one-to-many relationship @@ -26,7 +26,7 @@ Additionally, using the OData Expand query parameter, related entities can be tr ## Issues and Considerations ------------------------- -In the current Microsoft Graph implementation, support for navigation properties is limited to entities within the same backend service or the user entity. +In the current Microsoft Graph implementation, there are some limitations on the use of navigation properties that cross between backend services. These limitations are being eliminated over time, but it will be necessary to ensure support for any particular scenario.   Navigation properties defined within an entity are not returned when retreiving the representation of an entity unless explicity desired by a service. From c9d9029cd95b3f8a8524e75c1e900c3714cccf0a Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 26 Aug 2022 17:29:42 -0400 Subject: [PATCH 459/729] Fixed examples --- graph/patterns/navigation-property.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index e2d1e0de..5f0f23ab 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -28,9 +28,9 @@ Additionally, using the OData Expand query parameter, related entities can be tr In the current Microsoft Graph implementation, there are some limitations on the use of navigation properties that cross between backend services. These limitations are being eliminated over time, but it will be necessary to ensure support for any particular scenario.   -Navigation properties defined within an entity are not returned when retreiving the representation of an entity unless explicity desired by a service. +Navigation properties defined within an entity are not returned by default when retreiving the representation of an entity unless explicity desired by a service. -Implementing support for accessing the "$ref" of a navigation property allows a caller to return just the URL of related resource. e.g. `/user/23/manager/$ref`. This is useful when a client wishes to identity the related resource but doesn't need all of its properties. +Implementing support for accessing the "$ref" of a navigation property allows a caller to return just the URL of related resource. e.g. `/user/23/manager/$ref`. This is useful when a client wishes to identify the related resource but doesn't need all of its properties. ## When to Use this Pattern ------------------------ @@ -152,7 +152,7 @@ Content-Type: application/json { "displayName": "Bob", - "manager@bind": "https://graph.microsoft.com/v1.0/users/{managerId}" + "manager@odata.bind": "https://graph.microsoft.com/v1.0/users/{managerId}" } 204 No Content From 3f8cd0c94d50e04d1d79ec7c0013905f0d06adb7 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 26 Aug 2022 14:59:26 -0700 Subject: [PATCH 460/729] Add note about URL-encoding for nextLink --- azure/Guidelines.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 8bc1d758..9d314100 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -512,6 +512,8 @@ Note: To avoid potential collision of actions and resource ids, you should disal :white_check_mark: **DO** return a `nextLink` field with an absolute URL that the client can GET in order to retrieve the next page of the collection. +Note: The service is responsible for performing any URL-encoding required on the `nextLink` URL. + :white_check_mark: **DO** include any query parameters required by the service in `nextLink`, including `api-version`. :ballot_box_with_check: **YOU SHOULD** use `value` as the name of the top-level array field unless a more appropriate name is available. From 73aca77fe8a2503dd3882e2a89a1320836f7038b Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 2 Sep 2022 17:21:28 -0400 Subject: [PATCH 461/729] added examples --- graph/patterns/operations.md | 82 +++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index 4a7dbf6e..eb0986d7 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -6,12 +6,11 @@ Microsoft Graph API Design Pattern ## Problem -Sometimes when modeling a complex business domain, API designers need to model a business operation that effects one or multiple resources and has additional semantic meaning that cannot be expressed by HTTP methods. Modeling the operation via HTTP methods on each individual resource might be either inefficient or expose internal implementation details. In addition, the operation might produce observable side effects. +Sometimes when modeling a complex business domain, API designers need to model a business operation that effects one or multiple resources and has additional semantic meaning that cannot be expressed by HTTP methods. Modeling the operation via HTTP methods on each individual resource might be either inefficient or expose internal implementation details. ## Solution -To address these use cases, API designers can use operational resources such as functions or actions. -If the operation doesn't have any side effects and MUST return a single instance of a type or a collection of instances, then the designer MUST use OData functions; otherwise, the designer can model the operation as an action. +To address these use cases, API designers can use operational resources such as functions or actions. If the operation doesn't have any side effects and MUST return a single instance of a type or a collection of instances, then the designer SHOULD use OData functions; otherwise, the designer can model the operation as an action. ## When to use this pattern @@ -21,15 +20,14 @@ The operation pattern might be justified when a modeling operation represents on - a change of a resource (i.e., increment the value of a property) rather than a state (i.e., the final value of the property) - complex processing logic that shouldn't be exposed to the client -- operation parameters might convey a restricted set of option (i.e., a report that can has to specify a date range) +- operation parameters might convey a restricted set of option (i.e., a report that has to specify a date range) - the operation leverage some service-side data not exposed to (or easily retrieved in context by) the user. You can consider related patterns such as [long running operations](./long-running-operations.md) and [change tracking](./change-tracking.md). ## Issues and considerations -- Microsoft Graph does NOT support unbound actions or functions. Bound actions and functions are invoked on resources matching the type of the binding parameter. The binding parameter can be of any type, and parameter value MAY be Nullable. -For Microsoft Graph, actions and functions must have the `isBound="true"` attribute. The first parameter is the binding parameter. +- Microsoft Graph does NOT support unbound actions or functions. Bound actions and functions are invoked on resources matching the type of the binding parameter. The binding parameter can be of any type, and parameter value MAY be Nullable. For Microsoft Graph, actions and functions must have the `isBound="true"` attribute. The first parameter is the binding parameter. - Both actions and functions support overloading, meaning a schema might contain multiple actions or functions with the same name. The overload rules as per the OData [standard](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionOverloads) apply when adding parameters to actions and functions. @@ -46,29 +44,65 @@ For Microsoft Graph, actions and functions must have the `isBound="true"` attrib - The addition of a new mandatory not-nullable parameter to an existing action or function is a breaking change and is not allowed without proper versioning that is in accordance with our [deprecation guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/graph/deprecation.md). -## Example +## Examples + +### A user wants to forward email ``` - - - - - +POST https://graph.microsoft.com/v1.0/me/messages/AQMkADNkMmMxYzIwLWJkOTItNDczZC1hNmYyLWUwZjk2ZTljMDQyNQBGAAAD1dY5iRo4x0_pEqop6hOrQAcAeGCrbYV1-kiG-z9Rv6yHMgAAAgEJAAAAeGCrbYV1-kiG-z9Rv6yHMgABRxeUKgAAAA==/forward + +{ + "comment": "FYI", + "toRecipients": [ + { + "emailAddress": { + "address": "alex.darrow@microsoft.com", + "name": "Alex Darrow" + } + } + ] +} +``` +Response: +``` +HTTP/1.1 202 Accepted + + "cache-control": "private", + "client-request-id": "ca2d0416-a2c1-05af-df60-0921547a86e9", + "content-length": "0", + "request-id": "8b53016f-cc2b-4d9f-9818-bd6f0a5e3cd0" +``` - - - +`forward` operation is modeled as an asynchronous action bound to the Graph `event` entity type because the operation represents a complex business logic processed on the server side. +``` + + + + +``` - - - - +### A user wants to see recent application activities - - - - - +``` +GET https://graph.microsoft.com/v1.0/me/activities/recent +``` + +Response: + +``` +HTTP/1.1 200 OK +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(userActivity)", + "value": [] +} +``` +`recent` function will query the most recent historyItems and then pull related activities therefore the operation represents a complex business logic processed on the server side. This operation doesn't change any server data and is a good fit for a function. The function is bound to the collection of `userActivity` entity type. + +``` + + + + ``` From 649062b6d54689ff94d99ce47495a6a7dddc5ee1 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 2 Sep 2022 18:18:56 -0400 Subject: [PATCH 462/729] added report example --- graph/patterns/operations.md | 46 +++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index eb0986d7..70c4c1a6 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -14,8 +14,6 @@ To address these use cases, API designers can use operational resources such as ## When to use this pattern -The operations pattern is well suited to use cases that cannot be modeled as a single HTTP method on a resource and require either multiple round trips to complete a single logical operation or produce one or multiple side effects. - The operation pattern might be justified when a modeling operation represents one or combination of the following: - a change of a resource (i.e., increment the value of a property) rather than a state (i.e., the final value of the property) @@ -27,7 +25,7 @@ You can consider related patterns such as [long running operations](./long-runni ## Issues and considerations -- Microsoft Graph does NOT support unbound actions or functions. Bound actions and functions are invoked on resources matching the type of the binding parameter. The binding parameter can be of any type, and parameter value MAY be Nullable. For Microsoft Graph, actions and functions must have the `isBound="true"` attribute. The first parameter is the binding parameter. +- Microsoft Graph does NOT support unbound actions or functions. Bound actions and functions MUST must have the `isBound="true"` attribute and a binding parameter. Bound operations are invoked on resources matching the type of the binding parameter.The first parameter of a bound operation is always the binding parameter.The binding parameter can be of any type, and parameter value MAY be Nullable. - Both actions and functions support overloading, meaning a schema might contain multiple actions or functions with the same name. The overload rules as per the OData [standard](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionOverloads) apply when adding parameters to actions and functions. @@ -106,3 +104,45 @@ HTTP/1.1 200 OK ``` +### Get a report that provides the number of active users using Microsoft Edge + +``` +https://graph.microsoft.com/beta/reports/getBrowserUserCounts(period='D7') +``` + +Response: + +``` +HTTP/1.1 200 OK +Content-Type: application/json +Content-Length: 205 + +{ + "value":[ + { + "reportRefreshDate":"2021-04-17", + "reportPeriod":7, + "userCounts":[ + { + "reportDate":"2021-04-17", + "edge":413 + }, + { + "reportDate":"2021-04-16", + "edge":883 + } + ] + } + ] +} +``` + +`getBrowserUserCounts` operation doesn't change any server data and is a good fit for a function.`period` operation parameter convey a restricted set of options representing the number of days over which the report is aggregated. The report supports only 7,30,90, or 180 days. In addition the function doesn't return a Graph resource but streams response data in JSON or CSV formats. + +``` + + + + + +``` \ No newline at end of file From f5c1c9169dd5eb692fe0c5549df5b28ca4b2af64 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 2 Sep 2022 18:22:41 -0400 Subject: [PATCH 463/729] identation --- graph/patterns/operations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index 70c4c1a6..c94e7db4 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -73,7 +73,7 @@ HTTP/1.1 202 Accepted `forward` operation is modeled as an asynchronous action bound to the Graph `event` entity type because the operation represents a complex business logic processed on the server side. ``` - + @@ -99,7 +99,7 @@ HTTP/1.1 200 OK `recent` function will query the most recent historyItems and then pull related activities therefore the operation represents a complex business logic processed on the server side. This operation doesn't change any server data and is a good fit for a function. The function is bound to the collection of `userActivity` entity type. ``` - + From dad8070768e51f2c3f40bc1b246239f31f52c88b Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 2 Sep 2022 18:24:40 -0400 Subject: [PATCH 464/729] removed annotations --- graph/patterns/operations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index c94e7db4..f12dd820 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -140,7 +140,7 @@ Content-Length: 205 `getBrowserUserCounts` operation doesn't change any server data and is a good fit for a function.`period` operation parameter convey a restricted set of options representing the number of days over which the report is aggregated. The report supports only 7,30,90, or 180 days. In addition the function doesn't return a Graph resource but streams response data in JSON or CSV formats. ``` - + From 598608c124b1ab9f1611e6857fb0ad48c7e04f09 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Tue, 6 Sep 2022 17:23:16 -0400 Subject: [PATCH 465/729] updated based on comments --- graph/patterns/navigation-property.md | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 5f0f23ab..3edd17f3 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -26,7 +26,7 @@ Additionally, using the OData Expand query parameter, related entities can be tr ## Issues and Considerations ------------------------- -In the current Microsoft Graph implementation, there are some limitations on the use of navigation properties that cross between backend services. These limitations are being eliminated over time, but it will be necessary to ensure support for any particular scenario. +In the current Microsoft Graph implementation, there are some limitations on the use of navigation properties that cross between backend services. These limitations are being eliminated over time, but it will be necessary to ensure support for any particular scenario. [Limitations of the current implementation](https://dev.azure.com/msazure/One/_wiki/wikis/Microsoft%20Graph%20Partners/354352/Cross-workload-navigations?anchor=supported-scenarios) are documented internally.   Navigation properties defined within an entity are not returned by default when retreiving the representation of an entity unless explicity desired by a service. @@ -115,28 +115,12 @@ Content-Type: application/json Create a new user that references an existing manager ```http -POST /users/{id} +POST /users Content-Type: application/json { "displayName": "Bob", - "manager@bind": "https://graph.microsoft.com/v1.0/users/{managerId}" -} - -201 Created -``` - -Create a new user and the users manager and create a relationship between the two. - -```http -POST /users/{id} -Content-Type: application/json - -{ - "displayName": "Jim James", - "manager": { - "displayName": "Bob Boyce" - } + "manager@odata.bind": "https://graph.microsoft.com/v1.0/users/{managerId}" } 201 Created From 14c5b97e7c5fc71f0353b2ec57515f3989ac3e31 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 7 Sep 2022 10:25:47 -0400 Subject: [PATCH 466/729] formatting --- graph/patterns/navigation-property.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 3edd17f3..3e73c950 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -16,10 +16,10 @@ The use of foreign key properties to describe related resources is a weakly type ## Solution -------- -Navigation properties are an [OData convention](https://docs.microsoft.com/en-us/odata/webapi/model-builder-untyped#navigation-property) defined in the [CSDL Specification](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#_Toc38530365) that allows an API designer to describe a special kind of property in a model that references an related entity. In the HTTP API this property name translates to a path segment that can be appended to the URL of the primary resource in order to access a representation of the related resource. This prevents the client from needing to know any additional information on how to construct the URL to the related resource and the client does not need to retrieve the primary resource if it is only interested in the related resource. It is the responsibility of the API implementation to determine the Id of the related resource and return the representation of the related entity. +Navigation properties are an [OData convention](https://docs.microsoft.com/en-us/odata/webapi/model-builder-untyped#navigation-property) defined in the [CSDL Specification](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#_Toc38530365) that allows an API designer to describe a special kind of property in a model that references an related entity. In the HTTP API this property name translates to a path segment that can be appended to the URL of the primary resource in order to access a representation of the related resource. This prevents the client from needing to know any additional information on how to construct the URL to the related resource and the client does not need to retrieve the primary resource if it is only interested in the related resource. It is the responsibility of the API implementation to determine the Id of the related resource and return the representation of the related entity. For example: -e.g. /user/{userId}/manager # many-to-one relationship - /user/{userId}/messages # one-to-many relationship + - /user/{userId}/manager represents many-to-one relationship + - /user/{userId}/messages represents one-to-many relationship Additionally, using the OData Expand query parameter, related entities can be transcluded into the primary entity so both can be retrieved in a single round trip. From 61f31324917e9b97d5a3b63c38a7cbea3ddab18c Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 7 Sep 2022 11:37:04 -0400 Subject: [PATCH 467/729] replaced transcluded --- graph/patterns/navigation-property.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 3e73c950..a378bbbc 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -21,7 +21,7 @@ Navigation properties are an [OData convention](https://docs.microsoft.com/en-us - /user/{userId}/manager represents many-to-one relationship - /user/{userId}/messages represents one-to-many relationship -Additionally, using the OData Expand query parameter, related entities can be transcluded into the primary entity so both can be retrieved in a single round trip. +Additionally, using the OData Expand query parameter, related entities can be nested into the primary entity so both can be retrieved in a single round trip. ## Issues and Considerations ------------------------- From 706695953f1f3c07d4cb421ec5f9c70522500ba6 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Wed, 7 Sep 2022 08:38:35 -0700 Subject: [PATCH 468/729] Update azure/Guidelines.md --- azure/Guidelines.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 41403f5d..bb8d94ec 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -757,7 +757,8 @@ The purpose is to inform customers (when debugging/logging responses) that they :white_check_mark: **DO** add the 'azure-deprecating' header with a string value to all service operations in your service's contract file (cadl/swagger). -:white_check_mark: **DO** include this header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. NOTE: We do not want to scare customers with this header. +:white_check_mark: **DO** include this header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. +> NOTE: We do not want to scare customers with this header. :white_check_mark: **DO** make the header's value a semicolon-delimited string indicating a set of deprecations where each one indicates what is deprecating, when it is deprecating, and a URL to more information. From 509385add8bcb90069a79713ea1ae206a1132960 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Wed, 7 Sep 2022 08:41:12 -0700 Subject: [PATCH 469/729] Update azure/Guidelines.md Co-authored-by: Mike Kistler --- azure/Guidelines.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index bb8d94ec..9e1b744f 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -755,8 +755,6 @@ When the [API Versioning](#API-Versioning) guidance above cannot be followed and The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation and use a newer API version or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. The string is _not_ part of an API contract (except for the semi-colon delimiters) and may be changed/improved at any time without incurring a breaking change. -:white_check_mark: **DO** add the 'azure-deprecating' header with a string value to all service operations in your service's contract file (cadl/swagger). - :white_check_mark: **DO** include this header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. > NOTE: We do not want to scare customers with this header. From 875d35aeb978017b9d20bc8ff60abe65881191fa Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 7 Sep 2022 12:11:05 -0400 Subject: [PATCH 470/729] fixed forward example --- graph/patterns/operations.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/graph/patterns/operations.md b/graph/patterns/operations.md index f12dd820..ec7d4889 100644 --- a/graph/patterns/operations.md +++ b/graph/patterns/operations.md @@ -71,11 +71,12 @@ HTTP/1.1 202 Accepted "request-id": "8b53016f-cc2b-4d9f-9818-bd6f0a5e3cd0" ``` -`forward` operation is modeled as an asynchronous action bound to the Graph `event` entity type because the operation represents a complex business logic processed on the server side. +`forward` operation is modeled as an asynchronous action bound to the Graph `message` entity type because the operation represents a complex business logic processed on the server side. ``` - - + + + ``` @@ -145,4 +146,4 @@ Content-Length: 205 -``` \ No newline at end of file +``` From a653012e72da2aacdd56ea27884699fc43592d71 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Wed, 7 Sep 2022 13:40:22 -0400 Subject: [PATCH 471/729] Update azure/Guidelines.md --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 98d8bb0e..ab39f173 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -3,7 +3,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | -| 2022-Jun-16 | Updated URL guidelines for DNS Done Right | +| 2022-Sep-07 | Updated URL guidelines for DNS Done Right | | 2022-May-11 | Drop guidance on version discovery | | 2022-Mar-29 | Add guidelines about using durations | | 2022-Mar-25 | Update guideline for date values in headers to follow RFC 7231 | From 0530fcbf50c2e0a8d4c195e43e6369936141695e Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 23 Sep 2022 09:02:01 -0500 Subject: [PATCH 472/729] Add anchors to all Azure guidelines for direct linking --- azure/Guidelines.md | 323 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 263 insertions(+), 60 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index dd03bcf5..e42724b5 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,6 +1,13 @@ # Microsoft Azure REST API Guidelines - + + + + ## History @@ -63,6 +70,7 @@ Azure services must adhere to the HTTP specification, [RFC7231](https://tools.ie A Uniform Resource Locator (URL) is how developers access the resources of your service. Ultimately, URLs are how developers form a cognitive model of your service's resources. +
    :white_check_mark: **DO** use this URL pattern: ```text https://.../// @@ -78,26 +86,34 @@ Where: | service‑root | Service-specific path (ex: blobcontainer, myqueue) | resource‑collection | Name of the collection, unabbreviated, pluralized | resource‑id | Id of resource within the resource-collection. This MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. - + :white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. + :white_check_mark: **DO** return `414-URI Too Long` if a URL exceeds 2083 characters + :white_check_mark: **DO** treat service-defined URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request **MUST** fail with a `404-Not found` HTTP return code. Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' + :white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a JSON response body + :white_check_mark: **DO** restrict the characters in service-defined path segments to `0-9 A-Z a-z - . _ ~`, with `:` allowed only as described below to designate an action operation. + :ballot_box_with_check: **YOU SHOULD** restrict the characters allowed in user-specified path segments (i.e. path parameters values) to `0-9 A-Z a-z - . _ ~` (do not allow `:`). + :ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) + :heavy_check_mark: **YOU MAY** use these other characters in the URL path but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` + :heavy_check_mark: **YOU MAY** support a direct endpoint URL for performance/routing: ```text https://-../... @@ -108,8 +124,10 @@ Examples: - Response header ([RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4)): `content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2` - GUID format: `https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2` + :white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for `` or always a single verified domain. + :heavy_check_mark: **YOU MAY** use URLs as values ```text https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy @@ -122,14 +140,18 @@ Cloud applications embrace failure. Therefore, to enable customers to write faul ##### Exactly Once Behavior = Client Retries & Service Idempotency + :white_check_mark: **DO** ensure that _all_ HTTP methods are idempotent. + :ballot_box_with_check: **YOU SHOULD** use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent. -:heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response **MUST** return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#Repeatability-of-requests)). + +:heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response **MUST** return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#repeatability-of-requests)). ##### HTTP Return Codes + :white_check_mark: **DO** adhere to the return codes in the following table when the method completes synchronously and is successful: Method | Description | Response Status Code @@ -142,23 +164,31 @@ GET | Read (i.e. list) a resource collection | `200-OK` GET | Read the resource | `200-OK` DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` + :white_check_mark: **DO** return status code `202-Accepted` and follow the guidance in [Long-Running Operations & Jobs](#long-running-operations--jobs) when a PUT, POST, or DELETE method completes asynchronously. + :white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase + :white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a `200-OK` or `201-Created`. + :white_check_mark: **DO** return a `204-No Content` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return `404-Not Found`) + :white_check_mark: **DO** return a `403-Forbidden` when the user does not have access to the resource _unless_ this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be `404-Not Found`. [Rationale: a `403-Forbidden` is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.] + :white_check_mark: **DO** support caching and optimistic concurrency by honoring the the `If-Match`, `If-None-Match`, if-modified-since, and if-unmodified-since request headers and by returning the ETag and last-modified response headers #### HTTP Query Parameters and Header Values Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. -:white_check_mark: **DO** validate all query parameter and request header values and fail the operation with `400-Bad Request` if any value fails validation. Return an error response as described in the [Handling Errors](#Handling-errors) section indicating what is wrong so customer can diagnose the issue and fix it themselves. + +:white_check_mark: **DO** validate all query parameter and request header values and fail the operation with `400-Bad Request` if any value fails validation. Return an error response as described in the [Handling Errors](#handling-errors) section indicating what is wrong so customer can diagnose the issue and fix it themselves. + :white_check_mark: **DO** use the following table when translating strings: Data type | Document that string must be @@ -176,47 +206,53 @@ Array | One of a) a comma-separated list of values (preferred), or b) separ The table below lists the headers most used by Azure services: - Header Key | Applies to | Example ------------------- | ---------- | ------------- _authorization_ | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) -_x-ms-useragent_ | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) -traceparent | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) -tracecontext | Request | (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) +_x-ms-useragent_ | Request | (see [Distributed Tracing & Telemetry](#distributed-tracing--telemetry)) +traceparent | Request | (see [Distributed Tracing & Telemetry](#distributed-tracing--telemetry)) +tracecontext | Request | (see [Distributed Tracing & Telemetry](#distributed-tracing--telemetry)) accept | Request | application/json -If-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) -If-None-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#Conditional-Requests)) -If-Modified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) -If-Unmodified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#Conditional-Requests)) +If-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#conditional-requests)) +If-None-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#conditional-requests)) +If-Modified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#conditional-requests)) +If-Unmodified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#conditional-requests)) date | Both | Sun, 06 Nov 1994 08:49:37 GMT (see [RFC7231, Section 7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2)) _content-type_ | Both | application/merge-patch+json _content-length_ | Both | 1024 _x-ms-request-id_ | Response | 4227cdc5-9f48-4e84-921a-10967cb785a0 -ETag | Response | "67ab43" (see [Conditional Requests](#Conditional-Requests)) +ETag | Response | "67ab43" (see [Conditional Requests](#conditional-requests)) last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT -_x-ms-error-code_ | Response | (see [Handling Errors](#Handling-Errors)) -_azure-deprecating_ | Response | (see [Deprecating Behavior](#Deprecating-Behavior)) +_x-ms-error-code_ | Response | (see [Handling Errors](#handling-errors)) +_azure-deprecating_ | Response | (see [Deprecating Behavior Notification](#deprecating-behavior-notification)) retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)) - + :white_check_mark: **DO** support all headers shown in _italics_ + :white_check_mark: **DO** specify headers using kebab-casing + :white_check_mark: **DO** compare request header names using case-insensitivity + :white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it + :white_check_mark: **DO** accept date values in headers in HTTP-Date format and return date values in headers in the IMF-fixdate format as defined in [RFC7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT". Note: The RFC 7321 IMF-fixdate format is a "fixed-length and single-zone subset" of the RFC 1123 / RFC 5822 format, which means: a) year must be four digits, b) the seconds component of time is required, and c) the timezone must be GMT. + :white_check_mark: **DO** create an opaque value that uniquely identifies the request and return this value in the `x-ms-request-id` response header. Your service should include the `x-ms-request-id` value in error logs so that users can submit support requests for specific failures using this value. + :no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated + :no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. **Additional References** @@ -227,24 +263,31 @@ Your service should include the `x-ms-request-id` value in error logs so that us ### REpresentational State Transfer (REST) REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#uniform-resource-locators-urls) determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. -There are patterns, later in these guidelines, that describe how to invoke behavior on your service. See [this article in the Azure Architecture Center](https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design) for a more detailed discussion of REST API design patterns. +There are patterns, later in these guidelines, that describe how to invoke behavior on your service. See [this article in the Azure Architecture Center](https://docs.microsoft.com/azure/architecture/best-practices/api-design) for a more detailed discussion of REST API design patterns. When designing your service, it is important to optimize for the developer using your API. + :white_check_mark: **DO** focus heavily on clear & consistent naming + :white_check_mark: **DO** ensure your resource paths make sense + :white_check_mark: **DO** simplify operations with few required query parameters & JSON fields + :white_check_mark: **DO** establish clear contracts for string values + :white_check_mark: **DO** use proper response codes/bodies so customer can diagnose their own problems and fix them without contacting Azure support or the service team -#### JSON Resource Schema & Field Mutability +#### Resource Schema & Field Mutability + :white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH response, GET response, and POST request/response on a given URL path. The PATCH request schema should contain all the same fields with no required fields. This allows one SDK type for input/output operations and enables the response to be passed back in a request. + :white_check_mark: **DO** think about your resource's fields and how they are used: Field Mutability | Service Request's behavior for this field @@ -256,32 +299,36 @@ Field Mutability | Service Request's behavior for this field In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically _not_ become a nullable field in a SDK's data structure. This allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. + :white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. -:white_check_mark: **DO** use camel case for all JSON field names. Do not upper-case acronyms; use camel case. - -:white_check_mark: **DO** treat JSON field names with case-sensitivity. - -:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions (e.g. GUIDs) but avoid if at all possible. - + :white_check_mark: **DO** use GET for resource retrieval and return JSON in the response body + :white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. -:white_check_mark: **DO** use PUT with JSON for wholesale create/update operations. **NOTE:** If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT). + +:white_check_mark: **DO** use PUT with JSON for wholesale create/replace operations. **NOTE:** If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT). + :white_check_mark: **DO** use DELETE to remove a resource. -:white_check_mark: **DO** fail an operation with `400-Bad Request` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in [Handling errors](#Handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. + +:white_check_mark: **DO** fail an operation with `400-Bad Request` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in [Handling errors](#handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. + :heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. + :no_entry: **DO NOT** return secret fields via GET. For example, do not return `administratorPassword` in JSON. + :no_entry: **DO NOT** add fields to the JSON if the value is easily computable from other fields to avoid bloating the body. ##### Create / Update / Replace Processing Rules + :white_check_mark: **DO** follow the processing below to create/update/replace a resource: When using this method | if this condition happens | use this response code @@ -302,20 +349,27 @@ There are 2 kinds of errors: - An error where you expect customer code to gracefully recover at runtime - An error indicating a bug in customer code that is unlikely to be recoverable at runtime; the customer must just fix their code + :white_check_mark: **DO** return an `x-ms-error-code` response header with a string error code indicating what went wrong. *NOTE: `x-ms-error-code` values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future.* + :heavy_check_mark: **YOU MAY** implement the `x-ms-error-code` values as an enum with `"modelAsString": true` because it's possible add new values over time. In particular, it's only a breaking change if the same conditions result in a *different* top-level error code. + :warning: **YOU SHOULD NOT** add new top-level error codes to an existing API without bumping the service version. + :white_check_mark: **DO** carefully craft unique `x-ms-error-code` string values for errors that are recoverable at runtime. Reuse common error codes for usage errors that are not recoverable. + :heavy_check_mark: **YOU MAY** group common customer code errors into a few `x-ms-error-code` string values. + :white_check_mark: **DO** ensure that the top-level error's `code` value is identical to the `x-ms-error-code` header's value. + :white_check_mark: **DO** provide a response body with the following structure: **ErrorResponse** : Object @@ -357,17 +411,31 @@ Example: } ``` + :white_check_mark: **DO** document the service's top-level error code strings; they are part of the API contract. + :heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are _not_ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. + :heavy_check_mark: **YOU MAY** add additional properties for any data values in your error message so customers don't resort to parsing your error message. For example, an error with `"message": "A maximum of 16 keys are allowed per account."` might also add a `"maximumKeys": 16` property. This is not part of your API contract and should only be used for diagnosing problems. *Note: Do not use this mechanism to provide information developers need to rely on in code (ex: the error message can give details about why you've been throttled, but the `Retry-After` should be what developers rely on to back off).* + :warning: **YOU SHOULD NOT** document specific error status codes in your OpenAPI/Swagger spec unless the "default" response cannot properly describe the specific error response (e.g. body schema is different). ### JSON + + +:white_check_mark: **DO** use camel case for all JSON field names. Do not upper-case acronyms; use camel case. + + +:white_check_mark: **DO** treat JSON field names with case-sensitivity. + + +:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions (e.g. GUIDs) but avoid if at all possible. + Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: Type | Description @@ -376,26 +444,37 @@ Services, and the clients that access them, may be written in multiple languages Number | Signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) String | Used for everything else + :white_check_mark: **DO** use integers within the acceptable range of JSON number. + :white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. + :white_check_mark: **DO** use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC3339 for date/time. + :white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable" across multiple programming languages. + :white_check_mark: **DO** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) for date/time. + :white_check_mark: **DO** use a fixed time interval to express durations e.g., milliseconds, seconds, minutes, days, etc., and include the time unit in the property name e.g., `backupTimeInMinutes` or `ttlSeconds`. + :heavy_check_mark: **YOU MAY** use [RFC3339 time intervals](https://wikipedia.org/wiki/ISO_8601#Durations) only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P3M" represents 3 months no matter how many days between the start and end dates, or "P1Y" represents 366 days on a leap year. The value must be round-trippable. + :white_check_mark: **DO** use [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) for UUIDs. + :heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. + :heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. Avoid arrays in other situations since arrays can be difficult and inefficient to work with, especially with JSON Merge Patch where the entire array needs to be read prior to any operation being applied to it. + :ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays whenever possible. #### Enums & SDKs (Client libraries) @@ -404,17 +483,23 @@ It is common for strings to have an explicit set of values. These are often refl However, it is not uncommon for the set of values to grow over the life of a service. For this reason, Microsoft's tooling uses the concept of an "extensible enum," which indicates that the set of values should be treated as only a _partial_ list. This indicates to client libraries and customers that values of the enumeration field should be effectively treated as strings and that undocumented value may returned in the future. This enables the set of values to grow over time while ensuring stability in client libraries and customer code. + :ballot_box_with_check: **YOU SHOULD** use extensible enumerations unless you are positive that the symbol set will NEVER change over time. + :white_check_mark: **DO** document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow. + :no_entry: **DO NOT** remove values from your enumeration list as this breaks customer code. #### Polymorphic types + + :warning: **YOU SHOULD NOT** use polymorphic JSON types because they greatly complicate the customer code due to runtime dynamic casts and the introduction of new types in the future. If you can't avoid them, then follow the guideline below. + :white_check_mark: **DO** define a `kind` field indicating the kind of the resource and include any kind-specific fields in the body. Below is an example of JSON for a Rectangle and Circle: @@ -457,6 +542,7 @@ Both Rectangle and Circle have common fields: `kind`, `fillColor`, `lineColor`, ### Performing an Action The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image or rebooting a VM. It is also sometimes useful to perform an action on a collection. + :ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a resource **URL Pattern** ```text @@ -468,6 +554,7 @@ https://...//:? https://.../users/Bob:grant?access=read ``` + :ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a collection **URL Pattern** ```text @@ -481,22 +568,31 @@ https://.../users:grant?access=read Note: To avoid potential collision of actions and resource ids, you should disallow the use of the ":" character in resource ids. + :white_check_mark: **DO** use a POST operation for any action on a resource or collection. + :white_check_mark: **DO** support the Repeatability-Request-ID & Repeatability-First-Sent request headers if the action needs to be idempotent if retries occur. + :white_check_mark: **DO** return a `200-OK` when the action completes synchronously and successfully. + :ballot_box_with_check: **YOU SHOULD** use a verb as the `` component of the path. + :no_entry: **DO NOT** use an action operation when the operation behavior could reasonably be defined as one of the standard REST Create, Read, Update, Delete, or List operations. ### Collections + :white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources. + :ballot_box_with_check: **YOU SHOULD** support paging today if there is ever a chance in the future that the number of items can grow to be very large. -> NOTE: It is a breaking change to add paging in the future +NOTE: It is a breaking change to add paging in the future + + :heavy_check_mark: **YOU MAY** expose an operation that lists your resources by supporting a GET method with a URL to a resource-collection (as opposed to a resource-id). **Example Response Body** @@ -512,25 +608,35 @@ Note: To avoid potential collision of actions and resource ids, you should disal } ``` + :white_check_mark: **DO** include the _id_ field and _etag_ field (if supported) for each item as this allows the customer to modify the item in a future operation. + :white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). + :white_check_mark: **DO** return a `nextLink` field with an absolute URL that the client can GET in order to retrieve the next page of the collection. Note: The service is responsible for performing any URL-encoding required on the `nextLink` URL. + :white_check_mark: **DO** include any query parameters required by the service in `nextLink`, including `api-version`. + :ballot_box_with_check: **YOU SHOULD** use `value` as the name of the top-level array field unless a more appropriate name is available. + :no_entry: **DO NOT** return the `nextLink` field at all when returning the last page of the collection. + :no_entry: **DO NOT** return the `nextLink` field with a value of null. + :heavy_check_mark: **YOU MAY** support the following query parameters allowing customers to control the list operation: Parameter name | Type | Description @@ -543,22 +649,29 @@ Parameter name | Type | Description `select` | string array | a list of field names to be returned for each resource `expand` | string array | a list of the related resources to be included in line with each resource + :white_check_mark: **DO** return an error if the client specifies any parameter not supported by the service. + :white_check_mark: **DO** treat these query parameter names as case-sensitive. + :white_check_mark: **DO** apply `select` or `expand` options after applying all the query options in the table above. + :white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. + :no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). #### `filter` + :heavy_check_mark: **YOU MAY** support `filter`ing of the results of a list operation with the `filter` query parameter. The value of the `filter` option is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response. + :white_check_mark: **DO** omit all resources from the collection for which the `filter` expression evaluates to false or to null, or references properties that are unavailable due to permissions. Example: return all Products whose Price is less than $10.00 @@ -587,8 +700,10 @@ not | Logical negation | not price le 3.5 **Grouping Operators** | | ( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 + :white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#handling-errors) section if a client includes an operator in a `filter` expression that is not supported by the operation. + :white_check_mark: **DO** use the following operator precedence for supported operators when evaluating `filter` expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: | Group | Operator | Description @@ -604,7 +719,8 @@ not | Logical negation | not price le 3.5 | Conditional AND | and | Logical And | | Conditional OR | or | Logical Or | -> :heavy_check_mark: **YOU MAY** support orderby and `filter` functions such as concat and contains. For more information, see [odata Canonical Functions](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360979). + +:heavy_check_mark: **YOU MAY** support orderby and `filter` functions such as concat and contains. For more information, see [odata Canonical Functions](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360979). ##### Operator examples The following examples illustrate the use and semantics of each of the logical operators. @@ -641,6 +757,7 @@ GET https://api.contoso.com/products?`filter`=(name eq 'Milk' or name eq 'Eggs') #### orderby + :heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the `orderby` query parameter. *NOTE: It is unusual for a service to support `orderby` because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results.* @@ -649,15 +766,20 @@ A special case of such an expression is a property path terminating on a primiti Each expression in the `orderby` parameter value may include the suffix "asc" for ascending or "desc" for descending, separated from the expression by one or more spaces. + :white_check_mark: **DO** sort the collection in ascending order on an expression if "asc" or "desc" is not specified. + :white_check_mark: **DO** sort NULL values as "less than" non-NULL values. + :white_check_mark: **DO** sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on. + :white_check_mark: **DO** use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically. -:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#Handling-errors) section if the client requests sorting by a field that is not supported by the operation. + +:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#handling-errors) section if the client requests sorting by a field that is not supported by the operation. For example, to return all people sorted by name in ascending order: ```text @@ -677,27 +799,36 @@ will return all people whose name is David sorted in ascending order by hireDate ##### Considerations for sorting with pagination + :white_check_mark: **DO** use the same `filter`ing options and sort order for all pages of a paginated list operation response. ##### skip + :white_check_mark: **DO** define the `skip` parameter as an integer with a default and minimum value of 0. + :heavy_check_mark: **YOU MAY** allow clients to pass the `skip` query parameter to specify an offset into collection of the first resource to be returned. ##### top + + :heavy_check_mark: **YOU MAY** allow clients to pass the `top` query parameter to specify the maximum number of resources to return from the collection. If supporting `top`: :white_check_mark: **DO** define the `top` parameter as an integer with a minimum value of 1. If not specified, `top` has a default value of infinity. + :white_check_mark: **DO** return the collection's `top` number of resources (if available), starting from `skip`. ##### maxpagesize + :heavy_check_mark: **YOU MAY** allow clients to pass the `maxpagesize` query parameter to specify the maximum number of resources to include in a single page response. + :white_check_mark: **DO** define the `maxpagesize` parameter as an optional integer with a default value appropriate for the collection. + :white_check_mark: **DO** make clear in documentation of the `maxpagesize` parameter that the operation may choose to return fewer resources than the value specified. ### API Versioning @@ -708,30 +839,36 @@ Azure services need to change over time. However, when changing a service, there *NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy/) has tables (section 5) describing what kinds of changes are considered breaking. Breaking changes are allowable (due to security/compliance/etc.) if approved by the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) but only following ample communication to customers and a lengthy deprecation period.* + :white_check_mark: **DO** review any API changes with the Azure API Stewardship Board Clients specify the version of the API to be used in every request to the service, even requests to an `Operation-Location` or `nextLink` URL returned by the service. + :white_check_mark: **DO** use a required query parameter named `api-version` on every operation for the client to specify the API version. + :white_check_mark: **DO** use `YYYY-MM-DD` date values, with a `-preview` suffix for preview versions, as the valid values for `api-version`. ```text PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 ``` + :white_check_mark: **DO** use a later date for each new preview version When releasing a new preview, the service team may completely retire any previous preview versions after giving customers at least 90 days to upgrade their code -:white_check_mark: **DO** use a later date for successive preview versions. - + :no_entry: **DO NOT** introduce any breaking changes into the service. + :no_entry: **DO NOT** include a version number segment in any operation path. + :no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview `api-version` is '2021-06-04-preview', the GA version of the API **must be** a date later than 2021-06-04 + :no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. #### Use Extensible Enums @@ -755,7 +892,8 @@ While removing a value from an enum is a breaking change, adding value to an enu } ``` -> :ballot_box_with_check: **You SHOULD** use extensible enums unless you are positive that the symbol set will **NEVER** change over time. + +:ballot_box_with_check: **You SHOULD** use extensible enums unless you are positive that the symbol set will **NEVER** change over time. ### Deprecating Behavior Notification @@ -763,9 +901,11 @@ When the [API Versioning](#API-Versioning) guidance above cannot be followed and The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation and use a newer API version or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. The string is _not_ part of an API contract (except for the semi-colon delimiters) and may be changed/improved at any time without incurring a breaking change. -:white_check_mark: **DO** include this header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. + +:white_check_mark: **DO** include the `azure-deprecating` header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. > NOTE: We do not want to scare customers with this header. + :white_check_mark: **DO** make the header's value a semicolon-delimited string indicating a set of deprecations where each one indicates what is deprecating, when it is deprecating, and a URL to more information. Deprecations should use the following pattern: @@ -778,25 +918,25 @@ Where the following placeholders should be provided: - `date`: the target date that this will be deprecated. This should be expressed following the format in [ISO 8601](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "2022-10-31". - `url`: a fully qualified url that the user can follow to learn more about what is being deprecated, preferably to Azure Updates. -For example: +For example: ```text azure-deprecating: API version 2009-27-07 will retire on 2022-12-01 (https://azure.microsoft.com/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will retire on 2020-10-30 (https://azure.microsoft.com/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/) ``` + :no_entry: **DO NOT** introduce this header without approval from [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) and an official deprecation notice on [Azure Updates](https://azure.microsoft.com/updates/). + ### Repeatability of requests The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. + :ballot_box_with_check: **YOU SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). - The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. - A service advertises support for repeatability requests by adding the `Repeatability-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. - When understood, all endpoints co-located behind a DNS name **MUST** understand the header. This means that a service **MUST NOT** ignore the presence of a header for any endpoints behind the DNS name, but rather fail the request containing a `Repeatability-Request-ID` header if that particular endpoint lacks support for repeatable requests. Such partial support **SHOULD** be avoided due to the confusion it causes for clients. - -[OpenAPI Specification]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md - ### Long-Running Operations & Jobs When the processing for an operation may take a significant amount of time to complete, it should be @@ -806,8 +946,10 @@ through another API call. See the [Long Running Operations section](./ConsiderationsForServiceDesign.md#long-running-operations) in Considerations for Service Design for an introduction to the design of long-running operations. + :white_check_mark: **DO** implement an operation as an LRO if the 99th percentile response time is greater than 1s. + :no_entry: **DO NOT** implement PATCH as an LRO. If LRO update is required it must be implemented with POST. In rare instances where an operation may take a _very long_ time to complete, e.g. longer than 15 minutes, @@ -824,58 +966,78 @@ These are described in the following two sections. A POST or DELETE long-running operation accepts a request from the client to initiate the operation processing and returns a [status monitor](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.3) that reports the operation's progress. + :no_entry: **DO NOT** use a long-running POST to create a resource -- use PUT as described below. + :white_check_mark: **DO** allow the client to pass an `Operation-Id` header with an ID for the operation's status monitor. + :white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. + :white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header matches an existing operation unless the request is identical to the prior request (a retry scenario). + :white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. + :white_check_mark: **DO** return a `202-Accepted` status code from the request that initiates an LRO if the processing of the operation was successfully initiated (except for "PUT with additional processing" type LRO). + :warning: **YOU SHOULD NOT** return any other `2xx` status code from the initial request of an LRO -- return `202-Accepted` and a status monitor even if processing was completed before the initiating request returns. + :white_check_mark: **DO** return a status monitor in the response body as described in [Obtaining status and results of long-running operations](#obtaining-status-and-results-of-long-running-operations). + :ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation. + :ballot_box_with_check: **YOU SHOULD** include the `api-version` query parameter in the `Operation-Location` header with the same version passed on the initial request if it is required by the get operation on the status monitor. -:ballot_box_with_check: **YOU SHOULD** allow any valid value of the `api-version` query parameter to be used in the get operation on the status monitor. - #### PUT operation with additional long-running processing For a PUT (create or replace) with additional long-running processing: + :white_check_mark: **DO** allow the client to pass an `Operation-Id` header with a ID for the status monitor for the operation. + :white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. + :white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header that matches an existing operation unless the request is identical to the prior request (a retry scenario). + :white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. + :white_check_mark: **DO** return a `201-Created` status code for create or `200-OK` for replace from the initial request with a representation of the resource if the resource was created successfully. + :white_check_mark: **DO** include an `Operation-Id` header in the response with the ID of the status monitor for the operation. + :white_check_mark: **DO** include response headers with any additional values needed for a GET request to the status monitor (e.g. location). + :ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation. + :ballot_box_with_check: **YOU SHOULD** include the `api-version` query parameter in the `Operation-Location` header with the same version passed on the initial request if it is required by the get operation on the status monitor. -:ballot_box_with_check: **YOU SHOULD** allow any valid value of the `api-version` query parameter to be used in the get operation on the status monitor. - #### Obtaining status and results of long-running operations For all long-running operations, the client will issue a GET on a status monitor resource to obtain the current status of the operation. + :white_check_mark: **DO** support the GET method on the status monitor endpoint that returns a `200-OK` response with the current state of the status monitor. + +:ballot_box_with_check: **YOU SHOULD** allow any valid value of the `api-version` query parameter to be used in the get operation on the status monitor. + + :white_check_mark: **DO** return a status monitor in the response body that conforms with the following structure: **OperationStatus** : Object @@ -888,39 +1050,51 @@ Property | Type | Required | Description `result` | object | | Only for POST action-type LRO, the results of the operation when completed successfully additional
    properties | | | Additional named or dynamic properties of the operation + :white_check_mark: **DO** include the `id` of the operation and any other values needed for the client to form a GET request to the status monitor (e.g. a `location` path parameter). + :white_check_mark: **DO** include a `Retry-After` header in the response to GET requests to the status monitor if the operation is not complete. The value of this header should be an integer number of seconds to wait before making the next request to the status monitor. + :white_check_mark: **DO** include the `result` property (if any) in the status monitor for a POST action-type long-running operation when the operation completes successfully. + :no_entry: **DO NOT** include a `result` property in the status monitor for a long-running operation that is not a POST action-type long-running operation. + :white_check_mark: **DO** retain the status monitor resource for some publicly documented period of time (at least 24 hours) after the operation completes. ### Bring your own Storage (BYOS) -Many services need to store and retrieve data files. For this scenario, the service should not implement its own -storage APIs and should instead leverage the existing Azure Storage service. When doing this, the customer -"owns" the storage account and just tells your service to use it. Colloquially, we call this Bring Your Own Storage as the customer is bringing their storage account to another service. BYOS provides significant benefits to service implementors: security, performance, uptime, etc. And, of course, most Azure customers are already familiar with the Azure Storage service. +Many services need to store and retrieve data files. For this scenario, the service should not implement its own +storage APIs and should instead leverage the existing Azure Storage service. When doing this, the customer +"owns" the storage account and just tells your service to use it. Colloquially, we call this Bring Your Own Storage as the customer is bringing their storage account to another service. BYOS provides significant benefits to service implementors: security, performance, uptime, etc. And, of course, most Azure customers are already familiar with the Azure Storage service. While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS provides the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders. -:white_check_mark: **DO** use the Bring Your Own Storage pattern + +:white_check_mark: **DO** use the Bring Your Own Storage pattern. + :white_check_mark: **DO** use a blob prefix for a logical folder (avoid terms such as ```directory```, ```folder```, or ```path```). -:no_entry: **DO NOT** require a fresh container per operation + +:no_entry: **DO NOT** require a fresh container per operation. -:white_check_mark: **DO** use managed identity and Role Based Access Control ([RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview)) as the mechanism allowing customers to grant permission to their Storage account to your service. + +:white_check_mark: **DO** use managed identity and Role Based Access Control ([RBAC](https://docs.microsoft.com/azure/role-based-access-control/overview)) as the mechanism allowing customers to grant permission to their Storage account to your service. + :white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions. + :white_check_mark: **DO** Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. -##### Handling 'downstream' errors +#### Handling 'downstream' errors It is not uncommon to rely on other services, e.g. storage, when implementing your service. Inevitably, the services you depend on will fail. In these situations, you can include the downstream error code and text in the inner-error of the response body. This provides a consistent pattern for handling errors in the services you depend upon. + :white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. #### Working with files @@ -929,21 +1103,30 @@ Generally speaking, there are two patterns that you will encounter when working ##### Single file access Designing an API for accessing a single file, depending on your scenario, is relatively straight forward. -:heavy_check_mark: **YOU MAY** use a Shared Access Signature [SAS](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. + +:heavy_check_mark: **YOU MAY** use a Shared Access Signature [SAS](https://docs.microsoft.com/azure/storage/common/storage-sas-overview) to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. + :ballot_box_with_check: **YOU SHOULD** if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text. -:white_check_mark: **DO** return an HTTP status code representing the result of your service operation's behavior. + +:white_check_mark: **DO** return an HTTP status code representing the result of your service operation's behavior. -:white_check_mark: **DO** include the Storage error information in the 'inner-error' section of an error response if the error was the result of an internal Storage operation failure. This helps the client determine the underlying cause of the error, e.g.: a missing storage object or insufficient permissions. + +:white_check_mark: **DO** include the Storage error information in the 'inner-error' section of an error response if the error was the result of an internal Storage operation failure. This helps the client determine the underlying cause of the error, e.g.: a missing storage object or insufficient permissions. + :white_check_mark: **DO** allow the customer to specify a URL path to a single Storage object if your service requires access to a single file. -:heavy_check_mark: **YOU MAY** allow the customer to provide a [last-modified](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2) timestamp (in RFC1123 format) for read-only files. This allows the client to specify exactly which version of the files your service should use. When reading a file, your service passes this timestamp to Azure Storage using the [if-unmodified-since](https://datatracker.ietf.org/doc/html/rfc7232#section-3.4) request header. If the Storage operation fails with 412, the Storage object was modified and your service operation should return an appropriate 4xx status code and return the Storage error in your operation's 'inner-error' (see guideline above). + +:heavy_check_mark: **YOU MAY** allow the customer to provide a [last-modified](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2) timestamp (in RFC1123 format) for read-only files. This allows the client to specify exactly which version of the files your service should use. +When reading a file, your service passes this timestamp to Azure Storage using the [if-unmodified-since](https://datatracker.ietf.org/doc/html/rfc7232#section-3.4) request header. If the Storage operation fails with 412, the Storage object was modified and your service operation should return an appropriate 4xx status code and return the Storage error in your operation's 'inner-error' (see guideline above). -:white_check_mark: **DO** allow the customer to specify a URL path to a logical folder (via prefix and delimiter) if your service requires access to multiple files (within this folder). For more information, see [List Blobs API](https://docs.microsoft.com/en-us/rest/api/storageservices/list-blobs) + +:white_check_mark: **DO** allow the customer to specify a URL path to a logical folder (via prefix and delimiter) if your service requires access to multiple files (within this folder). For more information, see [List Blobs API](https://docs.microsoft.com/rest/api/storageservices/list-blobs) -:heavy_check_mark: **YOU MAY** offer an ```extensions``` field representing an array of strings indicating file extensions of desired blobs within the logical folder. + +:heavy_check_mark: **YOU MAY** offer an `extensions` field representing an array of strings indicating file extensions of desired blobs within the logical folder. A common pattern when working with multiple files is for your service to receive requests that contain the location(s) of files to process ("input") and a location(s) to place any files that result from processing ("output"). Note: the terms "input" and "output" are just examples; use terms more appropriate to your service's domain. @@ -964,14 +1147,18 @@ For example, a service's request body to configure BYOS may look like this: } ``` -Depending on the requirements of the service, there can be any number of "input" and "output" sections, including none. +Depending on the requirements of the service, there can be any number of "input" and "output" sections, including none. -:white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter". For "location", the customer must pass a URL to a blob prefix which represents a directory. For "delimiter", the customer must specify the delimiter character they desire to use in the location URL; typically "/" or "\". + +:white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter". For "location", the customer must pass a URL to a blob prefix which represents a directory. For "delimiter", the customer must specify the delimiter character they desire to use in the location URL; typically "/" or "\". + :heavy_check_mark: **YOU MAY** support the "lastModified" field for input directories (see guideline above). + :white_check_mark: **DO** support a "location" URL with a container-scoped SAS that has a minimum of `listing` and `read` permissions for input directories. + :white_check_mark: **DO** support a "location" URL with a container-scoped SAS that has a minimum of `write` permissions for output directories. ### Conditional Requests @@ -979,8 +1166,10 @@ When designing an API, you will almost certainly have to manage how your resourc Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an `ETag` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. An `ETag` is always set by the service and will enable you to _conditionally_ control how your service responds to requests, enabling you to provide predictable updates and more efficient access. + :ballot_box_with_check: **YOU SHOULD** return an `ETag` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). + :ballot_box_with_check: **YOU SHOULD** use `ETag`s consistently across your API, i.e. if you use an `ETag`, accept it on all other operations. You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). @@ -988,13 +1177,16 @@ You can learn more about conditional requests by reading [RFC7232](https://datat #### Cache Control One of the more common uses for `ETag` headers is cache control, also referred to a "conditional GET." This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). That is, using the value of the `ETag` , the server can determine if the resource has changed. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. -Implementing this strategy is relatively straightforward. First, you will return an `ETag` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-ETags) section provides guidance on how to properly calculate the value of your `ETag`. +Implementing this strategy is relatively straightforward. First, you will return an `ETag` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-etags) section provides guidance on how to properly calculate the value of your `ETag`. In these scenarios, when a request is made by the client an `ETag` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The `ETag` value can then be sent in subsequent requests as part of the `If-None-Match` header. This tells the service to compare the `ETag` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated `ETag` value in the header. + :ballot_box_with_check: **YOU SHOULD** implement conditional read strategies When supporting conditional read strategies: + + :white_check_mark: **DO** adhere to the following table for guidance: | GET Request | Return code | Response | @@ -1002,15 +1194,18 @@ When supporting conditional read strategies: | ETag value = `If-None-Match` value | `304-Not Modified` | no additional information | | ETag value != `If-None-Match` value | `200-OK` | Response body include the serialized value of the resource (typically JSON) | -For more control over caching, please refer to the `cache-control` [HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). +For more control over caching, please refer to the `cache-control` [HTTP header](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control). #### Optimistic Concurrency An `ETag` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of `ETags` and the [HTTP Request / Response Pattern](#http-request--response-pattern). -:warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. + +:warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. When supporting optimistic concurrency: + + :white_check_mark: **DO** adhere to the following table for guidance: | Operation | Header | Value | ETag check | Return code | Response | @@ -1025,26 +1220,34 @@ When supporting optimistic concurrency: #### Computing ETags The strategy that you use to compute the `ETag` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the `ETag`. Another common strategy for determining the value of an `ETag` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. + :ballot_box_with_check: **YOU SHOULD** use a hash of the representation of a resource rather than a last modified/version number -> While it may be tempting to use a revision/version number for the resource as the ETag, it interferes with client's ability to retry update requests. If a client sends a conditional update request, the service acts on the request, but the client never receives a response, a subsequent identical update will be seen as a conflict even though the retried request is attempting to make the same update. +While it may be tempting to use a revision/version number for the resource as the ETag, it interferes with client's ability to retry update requests. If a client sends a conditional update request, the service acts on the request, but the client never receives a response, a subsequent identical update will be seen as a conflict even though the retried request is attempting to make the same update. + :ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. + :ballot_box_with_check: **YOU SHOULD**, if supporting range requests, use a strong ETag in order to support caching. + :heavy_check_mark: **YOU MAY** use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. + :heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. + :white_check_box: **DO**, when supporting multiple representations (e.g. Content-Encodings) for the same resource, generate different ETag values for the different representations. ### Distributed Tracing & Telemetry Azure SDK client guidelines specify that client libraries must send telemetry data through the `User-Agent` header, `X-MS-UserAgent` header, and Open Telemetry. Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. + :white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. + :no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. **Additional References** From ae73300e1fafbc3c7d3f6fbc92f9c7a49cdb36ed Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 28 Sep 2022 10:06:46 -0500 Subject: [PATCH 473/729] Address PR review comments Co-authored-by: Weidong Xu --- azure/Guidelines.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index e42724b5..40d1c8cf 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -59,6 +59,7 @@ This document offers prescriptive guidance labeled as follows: ## Building Blocks: HTTP, REST, & JSON The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet; namely HTTP, REST, and JSON. This section provides you with a general understanding of how these technologies should be applied when creating your service. + ### HTTP Azure services must adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231). This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: @@ -260,6 +261,7 @@ Your service should include the `x-ms-request-id` value in error logs so that us - [Standard HTTP Headers](https://httpwg.org/specs/rfc7231.html#header.field.registration) - [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) + ### REpresentational State Transfer (REST) REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#uniform-resource-locators-urls) determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. @@ -273,7 +275,7 @@ When designing your service, it is important to optimize for the developer using :white_check_mark: **DO** ensure your resource paths make sense - + :white_check_mark: **DO** simplify operations with few required query parameters & JSON fields @@ -425,6 +427,7 @@ Example: :warning: **YOU SHOULD NOT** document specific error status codes in your OpenAPI/Swagger spec unless the "default" response cannot properly describe the specific error response (e.g. body schema is different). + ### JSON @@ -539,6 +542,7 @@ Both Rectangle and Circle have common fields: `kind`, `fillColor`, `lineColor`, ## Common API Patterns + ### Performing an Action The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image or rebooting a VM. It is also sometimes useful to perform an action on a collection. @@ -583,6 +587,7 @@ Note: To avoid potential collision of actions and resource ids, you should disal :no_entry: **DO NOT** use an action operation when the operation behavior could reasonably be defined as one of the standard REST Create, Read, Update, Delete, or List operations. + ### Collections :white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources. @@ -831,6 +836,7 @@ If supporting `top`: :white_check_mark: **DO** make clear in documentation of the `maxpagesize` parameter that the operation may choose to return fewer resources than the value specified. + ### API Versioning Azure services need to change over time. However, when changing a service, there are 2 requirements: @@ -895,6 +901,7 @@ While removing a value from an enum is a breaking change, adding value to an enu :ballot_box_with_check: **You SHOULD** use extensible enums unless you are positive that the symbol set will **NEVER** change over time. + ### Deprecating Behavior Notification When the [API Versioning](#API-Versioning) guidance above cannot be followed and the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) approve a [breaking change](#123-definition-of-a-breaking-change) to a specific API version it must be communicated to its callers. The API version that is being deprecated must add the `azure-deprecating` response header with a semicolon-delimited string notifying the caller what is being deprecated, when it will no longer function, and a URL linking to more information such as what new operation they should use instead. @@ -926,6 +933,7 @@ azure-deprecating: API version 2009-27-07 will retire on 2022-12-01 (https://azu :no_entry: **DO NOT** introduce this header without approval from [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) and an official deprecation notice on [Azure Updates](https://azure.microsoft.com/updates/). + ### Repeatability of requests The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. @@ -937,6 +945,7 @@ The ability to retry failed requests for which a client never received a respons - A service advertises support for repeatability requests by adding the `Repeatability-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. - When understood, all endpoints co-located behind a DNS name **MUST** understand the header. This means that a service **MUST NOT** ignore the presence of a header for any endpoints behind the DNS name, but rather fail the request containing a `Repeatability-Request-ID` header if that particular endpoint lacks support for repeatable requests. Such partial support **SHOULD** be avoided due to the confusion it causes for clients. + ### Long-Running Operations & Jobs When the processing for an operation may take a significant amount of time to complete, it should be @@ -1065,6 +1074,7 @@ additional
    properties | | | Additional named or dynamic properties of th :white_check_mark: **DO** retain the status monitor resource for some publicly documented period of time (at least 24 hours) after the operation completes. + ### Bring your own Storage (BYOS) Many services need to store and retrieve data files. For this scenario, the service should not implement its own storage APIs and should instead leverage the existing Azure Storage service. When doing this, the customer @@ -1161,6 +1171,7 @@ Depending on the requirements of the service, there can be any number of "input" :white_check_mark: **DO** support a "location" URL with a container-scoped SAS that has a minimum of `write` permissions for output directories. + ### Conditional Requests When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an `ETag` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. @@ -1240,6 +1251,7 @@ While it may be tempting to use a revision/version number for the resource as th :white_check_box: **DO**, when supporting multiple representations (e.g. Content-Encodings) for the same resource, generate different ETag values for the different representations. + ### Distributed Tracing & Telemetry Azure SDK client guidelines specify that client libraries must send telemetry data through the `User-Agent` header, `X-MS-UserAgent` header, and Open Telemetry. Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. From 05447fc27a96e466e8ca72c4095be8152a5ab2f7 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sat, 27 Aug 2022 12:04:54 -0600 Subject: [PATCH 474/729] Add naming guidelines for Azure services --- azure/ConsiderationsForServiceDesign.md | 61 ++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 268eeed9..a0c7fadb 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -84,6 +84,65 @@ As you build out your service and API, there are a number of decisions that can :ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. +## Use Good Names + +Good names for resources, properties, operations, and parameters are essential to a good developer experience. + +Resources are typically described by nouns. Resource and property names must be descriptive and easy to understand for end-users. Ease of understanding comes from familiarity and recognition; you should favor consistency with other Azure services, names in the product user interface, and industry standards. + +Names should aid developers in discovering functionality without having to constantly refer to documentation. +Use common patterns and standard conventions to aid developers in correctly guessing common property names and meanings. +Use verbose naming patterns and avoid abbreviations other than +well-known acronyms in your service domain. + +### Recommended Naming Conventions + +The following are recommended naming conventions for Azure services: + +:heavy_check_mark: **DO** use singular nouns for resource (schema) names. + +:heavy_check_mark: **DO** use plural nouns for collections (for listing type or collection properties). + +:ballot_box_with_check: **YOU SHOULD** case all acronyms the same as a normal word. + +For example, `nextUrl` and not `nextURL`. + +:ballot_box_with_check: **YOU SHOULD** use "at" suffix in names of `date-time` values. + +:ballot_box_with_check: **YOU SHOULD** use "is" prefix in names of `boolean` values. + +:ballot_box_with_check: **YOU SHOULD** use a suffix of the unit of measurement for values with a clear unit of measurement (such as bytes, miles, and so on). Use a generally accepted abbreviation for the units (e.g. "km" rather than "kilometers") when appropriate. + +:ballot_box_with_check: **YOU SHOULD** use an int for time durations and include the time units in the name. + +For example, `expirationDays` as `int` and not `expiration` as `date-time`. + +:ballot_box_with_check: **YOU SHOULD** prefix property names for properties concerning a different resource, particular for resource identifiers. + +:warning: **YOU SHOULD NOT** use brand names in resource or property names. + +:warning: **YOU SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. + +:no_entry: **DO NOT** use redundant words in names. + +For example, `/phones/number` and not `phone/phoneNumber`. + +:no_entry: **DO NOT** use a "request", "response", or "collection" suffix on resource names. + +### Common names + +The following are recommended names for properties that match the associated description: + +| Name | Description | +|------------- | --- | +| createdAt | The date-time that the resource was created | +| updatedAt | The date-time that the resource was last updated/modified | +| kind | The discriminator value for a polymorphic resource | + +### `name` vs `id` + +The identifier of a resource should be named `id`. This holds even in the case where the identifier is assigned by the user with a PUT method. + ## Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. @@ -111,7 +170,7 @@ One common area of friction for developers is _polymorphism_ -- where a value ma Polymorphism can be beneficial in certain cases, e.g. as a way to express inheritance, but also creates friction because it requires the value to be introspected before being processed and cannot be represented in a natural/useful way in many type-safe languages. -:ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint **SHOULD** work with a single type to avoid problems during SDK creation. +:ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint **YOU SHOULD** work with a single type to avoid problems during SDK creation. :ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. From 99e26b287266927942db93d4f23719b64babe2d7 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 1 Sep 2022 12:55:33 -0500 Subject: [PATCH 475/729] Apply suggestions from PR review Co-authored-by: Jeffrey Richter Co-authored-by: Heath Stewart --- azure/ConsiderationsForServiceDesign.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index a0c7fadb..bb5244ef 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -88,7 +88,7 @@ As you build out your service and API, there are a number of decisions that can Good names for resources, properties, operations, and parameters are essential to a good developer experience. -Resources are typically described by nouns. Resource and property names must be descriptive and easy to understand for end-users. Ease of understanding comes from familiarity and recognition; you should favor consistency with other Azure services, names in the product user interface, and industry standards. +Resources are described by nouns. Resource and property names must be descriptive and easy for customers to understand. Ease of understanding comes from familiarity and recognition; you should favor consistency with other Azure services, names in the product's portal/user interface, and industry standards. Names should aid developers in discovering functionality without having to constantly refer to documentation. Use common patterns and standard conventions to aid developers in correctly guessing common property names and meanings. @@ -107,11 +107,11 @@ The following are recommended naming conventions for Azure services: For example, `nextUrl` and not `nextURL`. -:ballot_box_with_check: **YOU SHOULD** use "at" suffix in names of `date-time` values. +☑️ **YOU SHOULD** use "At" suffix in names of `date-time` values. :ballot_box_with_check: **YOU SHOULD** use "is" prefix in names of `boolean` values. -:ballot_box_with_check: **YOU SHOULD** use a suffix of the unit of measurement for values with a clear unit of measurement (such as bytes, miles, and so on). Use a generally accepted abbreviation for the units (e.g. "km" rather than "kilometers") when appropriate. +☑️ **YOU SHOULD** use a suffix of the unit of measurement for values with a clear unit of measurement (such as bytes, miles, and so on). Use a generally accepted abbreviation for the units (e.g. "Km" rather than "Kilometers") when appropriate. :ballot_box_with_check: **YOU SHOULD** use an int for time durations and include the time units in the name. @@ -121,7 +121,7 @@ For example, `expirationDays` as `int` and not `expiration` as `date-time`. :warning: **YOU SHOULD NOT** use brand names in resource or property names. -:warning: **YOU SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. +⚠️ **YOU SHOULD NOT** use acronyms or abbreviations unless they are broadly understood for example, "ID" or "URL", but not "Num" for "number". :no_entry: **DO NOT** use redundant words in names. @@ -141,7 +141,7 @@ The following are recommended names for properties that match the associated des ### `name` vs `id` -The identifier of a resource should be named `id`. This holds even in the case where the identifier is assigned by the user with a PUT method. +The identifier of a resource should be suffixed with `Id`. This holds even in the case where the identifier is assigned by the user with a PUT/PATCH method. ## Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. From 4736c10a499cbec1989f3804502edfb0c806a082 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 1 Sep 2022 15:21:43 -0500 Subject: [PATCH 476/729] More updates from PR review --- azure/ConsiderationsForServiceDesign.md | 41 +++++++++++++++---------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index bb5244ef..efd4b496 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -88,60 +88,69 @@ As you build out your service and API, there are a number of decisions that can Good names for resources, properties, operations, and parameters are essential to a good developer experience. -Resources are described by nouns. Resource and property names must be descriptive and easy for customers to understand. Ease of understanding comes from familiarity and recognition; you should favor consistency with other Azure services, names in the product's portal/user interface, and industry standards. +Resources are described by nouns. Resource and property names must be descriptive and easy for customers to understand. +Avoid names that refer to service implementation details, e.g. "TreeLeafNode", or simply describe the structure of value without +describing its purpose, e.g. "KeyValuePair". +Ease of understanding comes from familiarity and recognition; you should favor consistency with other Azure services, names in the product's portal/user interface, and industry standards. Names should aid developers in discovering functionality without having to constantly refer to documentation. Use common patterns and standard conventions to aid developers in correctly guessing common property names and meanings. Use verbose naming patterns and avoid abbreviations other than well-known acronyms in your service domain. +:heavy_check_mark: **DO** use the same name for the same concept and different names for different concepts wherever possible. + ### Recommended Naming Conventions The following are recommended naming conventions for Azure services: -:heavy_check_mark: **DO** use singular nouns for resource (schema) names. +:heavy_check_mark: **DO** name collections as plural nouns or plural noun phrases using correct English. + +:heavy_check_mark: **DO** name values that are not collections as singular nouns or singular noun phrases. -:heavy_check_mark: **DO** use plural nouns for collections (for listing type or collection properties). +:ballot_box_with_check: **YOU SHOULD** should place the adjective before the noun in names that contain both a noun and an adjective. -:ballot_box_with_check: **YOU SHOULD** case all acronyms the same as a normal word. +For example, `collectedItems` not `itemsCollected` -For example, `nextUrl` and not `nextURL`. +:ballot_box_with_check: **YOU SHOULD** case all acronyms as though they were regular words (i.e. lower camelCase). -☑️ **YOU SHOULD** use "At" suffix in names of `date-time` values. +For example, `nextUrl` not `nextURL`. -:ballot_box_with_check: **YOU SHOULD** use "is" prefix in names of `boolean` values. +:ballot_box_with_check: **YOU SHOULD** use "DateTime" suffix in names of `date-time` values. -☑️ **YOU SHOULD** use a suffix of the unit of measurement for values with a clear unit of measurement (such as bytes, miles, and so on). Use a generally accepted abbreviation for the units (e.g. "Km" rather than "Kilometers") when appropriate. +:ballot_box_with_check: **YOU SHOULD** use a suffix of the unit of measurement for values with a clear unit of measurement (such as bytes, miles, and so on). Use a generally accepted abbreviation for the units (e.g. "Km" rather than "Kilometers") when appropriate. :ballot_box_with_check: **YOU SHOULD** use an int for time durations and include the time units in the name. For example, `expirationDays` as `int` and not `expiration` as `date-time`. -:ballot_box_with_check: **YOU SHOULD** prefix property names for properties concerning a different resource, particular for resource identifiers. - :warning: **YOU SHOULD NOT** use brand names in resource or property names. -⚠️ **YOU SHOULD NOT** use acronyms or abbreviations unless they are broadly understood for example, "ID" or "URL", but not "Num" for "number". +:warning: **YOU SHOULD NOT** use acronyms or abbreviations unless they are broadly understood for example, "ID" or "URL", but not "Num" for "number". + +:warning: **YOU SHOULD NOT** use names that are reserved words in widely used programming languages. + +:no_entry: **DO NOT** use "is" prefix in names of `boolean` values. :no_entry: **DO NOT** use redundant words in names. For example, `/phones/number` and not `phone/phoneNumber`. -:no_entry: **DO NOT** use a "request", "response", or "collection" suffix on resource names. - ### Common names The following are recommended names for properties that match the associated description: | Name | Description | |------------- | --- | -| createdAt | The date-time that the resource was created | -| updatedAt | The date-time that the resource was last updated/modified | +| createdDateTime | The date-time that the resource was created | +| updatedDateTime | The date-time that the resource was last updated/modified | | kind | The discriminator value for a polymorphic resource | ### `name` vs `id` -The identifier of a resource should be suffixed with `Id`. This holds even in the case where the identifier is assigned by the user with a PUT/PATCH method. +:heavy_check_mark: **DO** use "Id" suffix for the name of rhe identifier of a resource. + +This holds even in the case where the identifier is assigned by the user with a PUT/PATCH method. ## Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. From 3031159bf0ab154a0245e1dd3f787e47ac30fe68 Mon Sep 17 00:00:00 2001 From: Darrel Date: Wed, 5 Oct 2022 10:08:41 -0400 Subject: [PATCH 477/729] Update graph/patterns/navigation-property.md --- graph/patterns/navigation-property.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index a378bbbc..fa571634 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -77,7 +77,7 @@ This navigation property could be described with the following CSDL: ``` - +`ContainsTarget` is set to false for clarity, this is the default value when the attribute is omitted. ### Retrieving a reference to a related entity ```http From ea4424679d8394ab1f3ad4b6799cad8c31e9cf9c Mon Sep 17 00:00:00 2001 From: Darrel Date: Wed, 5 Oct 2022 10:09:23 -0400 Subject: [PATCH 478/729] Update graph/patterns/navigation-property.md --- graph/patterns/navigation-property.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index fa571634..9a18a539 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -74,7 +74,7 @@ Content-Type: application/json This navigation property could be described with the following CSDL: ```xml - + ``` `ContainsTarget` is set to false for clarity, this is the default value when the attribute is omitted. From c28cf0e0011a5f723adebccd6a51653e9e836de1 Mon Sep 17 00:00:00 2001 From: Darrel Date: Wed, 5 Oct 2022 10:12:56 -0400 Subject: [PATCH 479/729] Update graph/patterns/navigation-property.md --- graph/patterns/navigation-property.md | 1 - 1 file changed, 1 deletion(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 9a18a539..848705d5 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -65,7 +65,6 @@ GET /users/{id}/manager Content-Type: application/json { - "@odata.type": "#microsoft.graph.user", "id": "6b3ee805-c449-46a8-aac8-8ff9cff5d213", "displayName": "Bob Boyce" } From 296e918671c7dd29a6414d996b2b0d40512bc8e5 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Wed, 5 Oct 2022 11:02:12 -0700 Subject: [PATCH 480/729] Document all LRO values in Azure Guidelines --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 40d1c8cf..bf73dd12 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1054,7 +1054,7 @@ For all long-running operations, the client will issue a GET on a status monitor Property | Type | Required | Description -------- | ----------- | :------: | ----------- `id` | string | true | The unique id of the operation -`status` | string | true | enum that includes terminal values "Succeeded", "Failed", "Canceled" +`status` | string | true | enum that includes values "NotStarted", "Running", "Succeeded", "Failed", and "Canceled" `error` | ErrorDetail | | Error object that describes the error when status is "Failed" `result` | object | | Only for POST action-type LRO, the results of the operation when completed successfully additional
    properties | | | Additional named or dynamic properties of the operation From 19707b081bd3a3e8e55d010a9dbc64c3ee934804 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 12 Oct 2022 08:29:49 -0500 Subject: [PATCH 481/729] Apply suggestions from PR review Co-authored-by: tg-msft --- azure/ConsiderationsForServiceDesign.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index efd4b496..05fcd457 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -86,7 +86,7 @@ As you build out your service and API, there are a number of decisions that can ## Use Good Names -Good names for resources, properties, operations, and parameters are essential to a good developer experience. +Good names for resources, properties, operations, and parameters are essential to a great developer experience. Resources are described by nouns. Resource and property names must be descriptive and easy for customers to understand. Avoid names that refer to service implementation details, e.g. "TreeLeafNode", or simply describe the structure of value without @@ -128,7 +128,7 @@ For example, `expirationDays` as `int` and not `expiration` as `date-time`. :warning: **YOU SHOULD NOT** use acronyms or abbreviations unless they are broadly understood for example, "ID" or "URL", but not "Num" for "number". -:warning: **YOU SHOULD NOT** use names that are reserved words in widely used programming languages. +:warning: **YOU SHOULD NOT** use names that are reserved words in widely used programming languages (including C#, Java, JavaScript/TypeScript, Python, C++, and Go). :no_entry: **DO NOT** use "is" prefix in names of `boolean` values. @@ -148,7 +148,7 @@ The following are recommended names for properties that match the associated des ### `name` vs `id` -:heavy_check_mark: **DO** use "Id" suffix for the name of rhe identifier of a resource. +:heavy_check_mark: **DO** use "Id" suffix for the name of the identifier of a resource. This holds even in the case where the identifier is assigned by the user with a PUT/PATCH method. From c39ce1dccdbac8b7963a85f8646f96d4c45d7532 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 14 Oct 2022 15:06:18 -0400 Subject: [PATCH 482/729] Moar updates --- graph/patterns/navigation-property.md | 51 ++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 3edd17f3..29a15827 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -7,7 +7,7 @@ Microsoft Graph API Design Pattern ## Problem -------- -It is often valuable to represent a relationship between resources in an API. This may be a many-to-one or a one-to-many relationship. +It is often valuable to represent a relationship between resources in an API. Relationships between resources are often implicitly represented by a property contained in one of the resources that provides a key to a related resource. Usually that information is returned in a representation as an id value and the property is named using a convention that identifies the target type of related resource. e.g. userId @@ -23,35 +23,70 @@ e.g. /user/{userId}/manager # many-to-one relationship Additionally, using the OData Expand query parameter, related entities can be transcluded into the primary entity so both can be retrieved in a single round trip. +These relationships can be described in CSDL as follows: + +```xml + + + + +``` + ## Issues and Considerations ------------------------- In the current Microsoft Graph implementation, there are some limitations on the use of navigation properties that cross between backend services. These limitations are being eliminated over time, but it will be necessary to ensure support for any particular scenario. [Limitations of the current implementation](https://dev.azure.com/msazure/One/_wiki/wikis/Microsoft%20Graph%20Partners/354352/Cross-workload-navigations?anchor=supported-scenarios) are documented internally.   -Navigation properties defined within an entity are not returned by default when retreiving the representation of an entity unless explicity desired by a service. +Navigation properties defined within an entity are not returned by default when retreiving the representation of an entity unless explicity desired by a service. The API can consumer can use the `expand` query parameterm, where supported, to retreive both the source and the target entity of the relationship in a single request. Implementing support for accessing the "$ref" of a navigation property allows a caller to return just the URL of related resource. e.g. `/user/23/manager/$ref`. This is useful when a client wishes to identify the related resource but doesn't need all of its properties. ## When to Use this Pattern ------------------------ -### "has a" relationships +### "Many-to-one" relationships The use of navigation properties is preferred over including an Id field to reference the related entity in a many-to-one relationship. Id values require a client to make two round trips to retrieve the details of a related entity. With a navigation property a client can retrieve a related entity in a single round trip. + +Many-to-one relationships are always non-contained relationships as the lifetime of the target cannot depend on the source. + +```xml + + + +``` + -Navigation properties are also useful when clients sometimes want to retrieve both the primary entity and the related entity in a single round trip. The `expand` query parameter makes this possible. +### "Zero-or-one-to-one" relationships -### "parent-child" relationships +These navigation properties can be used as a structural organization mechanism to separate properties of an entity in a way that is similar to how complex types are often used. The primary difference being that the target of the navigation property are not returned by default when the source entity is retreived. The use of the navigation properties over complex properties is preferred when the source and target information comes from different backend APIs. + +These relationships must be contained. + +```xml + + + +``` + +### "One-to-many" relationships Resources that contain a parent Id property in a child resource can utilize a navigation property in the parent resource that is declared as a collection of child resources. If desirable, a parent navigation property can also be created in the child resource to the parent resource. This is usually not necessary as the parent URL is a subset of child resource URL. The main use of this would be when retrieving child resources and choosing to expand properties of the parent resource so that both can be retrieved in a single request. `/invoice/{invoiceId}/items/{itemId}?expand=parentInvoice(select=invoiceDate,Customer)` -### "child-parent" relationships +```xml + + + + + + + +``` -One other use case is when child resources appear in a non-contained collection and there is a desire to access the canonical parent: +One-to-many relationships may be contained or non-contained relations. -`/me/pinnedChannels/{channelId}/team` ## Example ------- From 1147b6495515bfa22827e893b45db2856c83a71c Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 14 Oct 2022 19:11:22 -0400 Subject: [PATCH 483/729] Updates based on comments --- graph/patterns/navigation-property.md | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 6b29db14..3b06d5e1 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -94,7 +94,7 @@ One-to-many relationships may be contained or non-contained relations. ### Retrieving a related entity ```http -GET /users/{id}/manager +GET /users/{id}/manager?$select=id,displayName 200 OK Content-Type: application/json @@ -173,19 +173,6 @@ Content-Type: application/json "manager@odata.bind": "https://graph.microsoft.com/v1.0/users/{managerId}" } -204 No Content -``` - -Create a relationship between the user and the existing manager. - -```http -PUT /users/{id}/manager/$ref -Content-Type: application/json - -{ - "@odata.id": "https://graph.microsoft.com/v1.0/users/{managerId}" -} - 204 No Content ```   From da9888d4359ac034496bcfb908729c514de82686 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 18 Oct 2022 16:28:34 -0400 Subject: [PATCH 484/729] Updated guidelines to ref Azure & Graph guidelines --- Guidelines.md | 142 ++++++++++++++++++++++++++++---------------------- README.md | 6 +-- 2 files changed, 84 insertions(+), 64 deletions(-) diff --git a/Guidelines.md b/Guidelines.md index c9812c89..766f679f 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -20,137 +20,157 @@ To provide the smoothest possible experience for developers on platforms followi This document establishes the guidelines Microsoft REST APIs SHOULD follow so RESTful interfaces are developed consistently. +### **Guidance for Azure service teams** +Azure service teams should use the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. + +### **Guidance for Microsoft Graph service teams** +Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. + + ## 2. Table of contents -- [Microsoft REST API Guidelines Working Group](#microsoft-rest-api-guidelines-working-group) -- [1. Abstract](#1-abstract) -- [2. Table of contents](#2-table-of-contents) -- [3. Introduction](#3-introduction) +- [Microsoft REST API Guidelines](#microsoft-rest-api-guidelines) + - [Microsoft REST API Guidelines Working Group](#microsoft-rest-api-guidelines-working-group) +- [Microsoft REST API Guidelines](#microsoft-rest-api-guidelines-1) + - [1. Abstract](#1-abstract) + - [**Guidance for Azure service teams**](#guidance-for-azure-service-teams) + - [**Guidance for Microsoft Graph service teams**](#guidance-for-microsoft-graph-service-teams) + - [2. Table of contents](#2-table-of-contents) + - [3. Introduction](#3-introduction) - [3.1. Recommended reading](#31-recommended-reading) -- [4. Interpreting the guidelines](#4-interpreting-the-guidelines) + - [4. Interpreting the guidelines](#4-interpreting-the-guidelines) - [4.1. Application of the guidelines](#41-application-of-the-guidelines) - [4.2. Guidelines for existing services and versioning of services](#42-guidelines-for-existing-services-and-versioning-of-services) - [4.3. Requirements language](#43-requirements-language) - [4.4. License](#44-license) -- [5. Taxonomy](#5-taxonomy) + - [5. Taxonomy](#5-taxonomy) - [5.1. Errors](#51-errors) - [5.2. Faults](#52-faults) - [5.3. Latency](#53-latency) - [5.4. Time to complete](#54-time-to-complete) - [5.5. Long running API faults](#55-long-running-api-faults) -- [6. Client guidance](#6-client-guidance) + - [6. Client guidance](#6-client-guidance) - [6.1. Ignore rule](#61-ignore-rule) - [6.2. Variable order rule](#62-variable-order-rule) - [6.3. Silent fail rule](#63-silent-fail-rule) -- [7. Consistency fundamentals](#7-consistency-fundamentals) + - [7. Consistency fundamentals](#7-consistency-fundamentals) - [7.1. URL structure](#71-url-structure) - [7.2. URL length](#72-url-length) - [7.3. Canonical identifier](#73-canonical-identifier) - [7.4. Supported methods](#74-supported-methods) - - [7.4.1. POST](#741-post) - - [7.4.2. PATCH](#742-patch) - - [7.4.3. Creating resources via PATCH (UPSERT semantics)](#743-creating-resources-via-patch-upsert-semantics) - - [7.4.4. Options and link headers](#744-options-and-link-headers) + - [7.4.1. POST](#741-post) + - [7.4.2. PATCH](#742-patch) + - [7.4.3. Creating resources via PATCH (UPSERT semantics)](#743-creating-resources-via-patch-upsert-semantics) + - [7.4.4. Options and link headers](#744-options-and-link-headers) - [7.5. Standard request headers](#75-standard-request-headers) - [7.6. Standard response headers](#76-standard-response-headers) - [7.7. Custom headers](#77-custom-headers) - [7.8. Specifying headers as query parameters](#78-specifying-headers-as-query-parameters) - [7.9. PII parameters](#79-pii-parameters) - [7.10. Response formats](#710-response-formats) - - [7.10.1. Clients-specified response format](#7101-clients-specified-response-format) - - [7.10.2. Error condition responses](#7102-error-condition-responses) + - [7.10.1. Clients-specified response format](#7101-clients-specified-response-format) + - [7.10.2. Error condition responses](#7102-error-condition-responses) + - [ErrorResponse : Object](#errorresponse--object) + - [Error : Object](#error--object) + - [InnerError : Object](#innererror--object) + - [Examples](#examples) - [7.11. HTTP Status Codes](#711-http-status-codes) - [7.12. Client library optional](#712-client-library-optional) -- [8. CORS](#8-cors) + - [8. CORS](#8-cors) - [8.1. Client guidance](#81-client-guidance) - - [8.1.1. Avoiding preflight](#811-avoiding-preflight) + - [8.1.1. Avoiding preflight](#811-avoiding-preflight) - [8.2. Service guidance](#82-service-guidance) -- [9. Collections](#9-collections) + - [9. Collections](#9-collections) - [9.1. Item keys](#91-item-keys) - [9.2. Serialization](#92-serialization) - [9.3. Collection URL patterns](#93-collection-url-patterns) - - [9.3.1. Nested collections and properties](#931-nested-collections-and-properties) + - [9.3.1. Nested collections and properties](#931-nested-collections-and-properties) - [9.4. Big collections](#94-big-collections) - [9.5. Changing collections](#95-changing-collections) - [9.6. Sorting collections](#96-sorting-collections) - - [9.6.1. Interpreting a sorting expression](#961-interpreting-a-sorting-expression) + - [9.6.1. Interpreting a sorting expression](#961-interpreting-a-sorting-expression) - [9.7. Filtering](#97-filtering) - - [9.7.1. Filter operations](#971-filter-operations) - - [9.7.2. Operator examples](#972-operator-examples) - - [9.7.3. Operator precedence](#973-operator-precedence) + - [9.7.1. Filter operations](#971-filter-operations) + - [9.7.2. Operator examples](#972-operator-examples) + - [9.7.3. Operator precedence](#973-operator-precedence) - [9.8. Pagination](#98-pagination) - - [9.8.1. Server-driven paging](#981-server-driven-paging) - - [9.8.2. Client-driven paging](#982-client-driven-paging) - - [9.8.3. Additional considerations](#983-additional-considerations) + - [9.8.1. Server-driven paging](#981-server-driven-paging) + - [9.8.2. Client-driven paging](#982-client-driven-paging) + - [9.8.3. Additional considerations](#983-additional-considerations) - [9.9. Compound collection operations](#99-compound-collection-operations) - [9.10. Empty Results](#910-empty-results) -- [10. Delta queries](#10-delta-queries) + - [10. Delta queries](#10-delta-queries) - [10.1. Delta links](#101-delta-links) - [10.2. Entity representation](#102-entity-representation) - [10.3. Obtaining a delta link](#103-obtaining-a-delta-link) - [10.4. Contents of a delta link response](#104-contents-of-a-delta-link-response) - [10.5. Using a delta link](#105-using-a-delta-link) -- [11. JSON standardizations](#11-json-standardizations) + - [11. JSON standardizations](#11-json-standardizations) - [11.1. JSON formatting standardization for primitive types](#111-json-formatting-standardization-for-primitive-types) - [11.2. Guidelines for dates and times](#112-guidelines-for-dates-and-times) - - [11.2.1. Producing dates](#1121-producing-dates) - - [11.2.2. Consuming dates](#1122-consuming-dates) - - [11.2.3. Compatibility](#1123-compatibility) + - [11.2.1. Producing dates](#1121-producing-dates) + - [11.2.2. Consuming dates](#1122-consuming-dates) + - [11.2.3. Compatibility](#1123-compatibility) - [11.3. JSON serialization of dates and times](#113-json-serialization-of-dates-and-times) - - [11.3.1. The `DateLiteral` format](#1131-the-dateliteral-format) - - [11.3.2. Commentary on date formatting](#1132-commentary-on-date-formatting) + - [11.3.1. The `DateLiteral` format](#1131-the-dateliteral-format) + - [11.3.2. Commentary on date formatting](#1132-commentary-on-date-formatting) - [11.4. Durations](#114-durations) - [11.5. Intervals](#115-intervals) - [11.6. Repeating intervals](#116-repeating-intervals) -- [12. Versioning](#12-versioning) + - [12. Versioning](#12-versioning) - [12.1. Versioning formats](#121-versioning-formats) - - [12.1.1. Group versioning](#1211-group-versioning) + - [12.1.1. Group versioning](#1211-group-versioning) + - [Examples of group versioning](#examples-of-group-versioning) - [12.2. When to version](#122-when-to-version) - [12.3. Definition of a breaking change](#123-definition-of-a-breaking-change) -- [13. Long running operations](#13-long-running-operations) + - [13. Long running operations](#13-long-running-operations) - [13.1. Resource based long running operations (RELO)](#131-resource-based-long-running-operations-relo) - [13.2. Stepwise long running operations](#132-stepwise-long-running-operations) - - [13.2.1. PUT](#1321-put) - - [13.2.2. POST](#1322-post) - - [13.2.3. POST, hybrid model](#1323-post-hybrid-model) - - [13.2.4. Operations resource](#1324-operations-resource) - - [13.2.5. Operation resource](#1325-operation-resource) - - [13.2.6. Operation tombstones](#1326-operation-tombstones) - - [13.2.7. The typical flow, polling](#1327-the-typical-flow-polling) - - [13.2.8. The typical flow, push notifications](#1328-the-typical-flow-push-notifications) - - [13.2.9. Retry-After](#1329-retry-after) + - [13.2.1. PUT](#1321-put) + - [13.2.2. POST](#1322-post) + - [13.2.3. POST, hybrid model](#1323-post-hybrid-model) + - [13.2.4. Operations resource](#1324-operations-resource) + - [13.2.5. Operation resource](#1325-operation-resource) + - [Percent complete](#percent-complete) + - [Target resource location](#target-resource-location) + - [13.2.6. Operation tombstones](#1326-operation-tombstones) + - [13.2.7. The typical flow, polling](#1327-the-typical-flow-polling) + - [Example of the typical flow, polling](#example-of-the-typical-flow-polling) + - [13.2.8. The typical flow, push notifications](#1328-the-typical-flow-push-notifications) + - [Example of the typical flow, push notifications existing subscription](#example-of-the-typical-flow-push-notifications-existing-subscription) + - [13.2.9. Retry-After](#1329-retry-after) - [13.3. Retention policy for operation results](#133-retention-policy-for-operation-results) -- [14. Throttling, Quotas, and Limits](#14-throttling-quotas-and-limits) + - [14. Throttling, Quotas, and Limits](#14-throttling-quotas-and-limits) - [14.1. Principles](#141-principles) - [14.2. Return Codes (429 vs 503)](#142-return-codes-429-vs-503) - [14.3. Retry-After and RateLimit Headers](#143-retry-after-and-ratelimit-headers) - [14.4. Service Guidance](#144-service-guidance) - - [14.4.1. Responsiveness](#1441-responsiveness) - - [14.4.2. Rate Limits and Quotas](#1442-rate-limits-and-quotas) - - [14.4.3. Overloaded services](#1443-overloaded-services) - - [14.4.4. Example Response](#1444-example-response) + - [14.4.1. Responsiveness](#1441-responsiveness) + - [14.4.2. Rate Limits and Quotas](#1442-rate-limits-and-quotas) + - [14.4.3. Overloaded services](#1443-overloaded-services) + - [14.4.4. Example Response](#1444-example-response) - [14.5. Caller Guidance](#145-caller-guidance) - [14.6. Handling callers that ignore Retry-After headers](#146-handling-callers-that-ignore-retry-after-headers) -- [15. Push notifications via webhooks](#15-push-notifications-via-webhooks) + - [15. Push notifications via webhooks](#15-push-notifications-via-webhooks) - [15.1. Scope](#151-scope) - [15.2. Principles](#152-principles) - [15.3. Types of subscriptions](#153-types-of-subscriptions) - [15.4. Call sequences](#154-call-sequences) - [15.5. Verifying subscriptions](#155-verifying-subscriptions) - [15.6. Receiving notifications](#156-receiving-notifications) - - [15.6.1. Notification payload](#1561-notification-payload) + - [15.6.1. Notification payload](#1561-notification-payload) - [15.7. Managing subscriptions programmatically](#157-managing-subscriptions-programmatically) - - [15.7.1. Creating subscriptions](#1571-creating-subscriptions) - - [15.7.2. Updating subscriptions](#1572-updating-subscriptions) - - [15.7.3. Deleting subscriptions](#1573-deleting-subscriptions) - - [15.7.4. Enumerating subscriptions](#1574-enumerating-subscriptions) + - [15.7.1. Creating subscriptions](#1571-creating-subscriptions) + - [15.7.2. Updating subscriptions](#1572-updating-subscriptions) + - [15.7.3. Deleting subscriptions](#1573-deleting-subscriptions) + - [15.7.4. Enumerating subscriptions](#1574-enumerating-subscriptions) - [15.8. Security](#158-security) -- [16. Unsupported requests](#16-unsupported-requests) + - [16. Unsupported requests](#16-unsupported-requests) - [16.1. Essential guidance](#161-essential-guidance) - [16.2. Feature allow list](#162-feature-allow-list) - - [16.2.1. Error response](#1621-error-response) -- [17. Naming guidelines](#17-naming-guidelines) + - [16.2.1. Error response](#1621-error-response) + - [17. Naming guidelines](#17-naming-guidelines) - [17.1. Approach](#171-approach) - [17.2. Casing](#172-casing) - [17.3. Names to avoid](#173-names-to-avoid) @@ -160,10 +180,10 @@ This document establishes the guidelines Microsoft REST APIs SHOULD follow so RE - [17.7. Name properties](#177-name-properties) - [17.8. Collections and counts](#178-collections-and-counts) - [17.9. Common property names](#179-common-property-names) -- [18. Appendix](#18-appendix) + - [18. Appendix](#18-appendix) - [18.1. Sequence diagram notes](#181-sequence-diagram-notes) - - [18.1.1. Push notifications, per user flow](#1811-push-notifications-per-user-flow) - - [18.1.2. Push notifications, firehose flow](#1812-push-notifications-firehose-flow) + - [18.1.1. Push notifications, per user flow](#1811-push-notifications-per-user-flow) + - [18.1.2. Push notifications, firehose flow](#1812-push-notifications-firehose-flow) diff --git a/README.md b/README.md index b62534f1..8ac0bbf4 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ They may additionally create documents specific to their team, adding further gu We publish these guidelines here with the aim of fostering dialogue and learning in the API community at large. We further hope that these guidelines may encourage other organizations to create guidelines that are appropriate for them and in turn, if they are able, to publish theirs. -### Additional guidance for Azure service teams -Azure service teams should reference the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. +### Guidance for Azure service teams +Azure service teams should use companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. -### Additional guidance for Microsoft Graph service teams +### Guidance for Microsoft Graph service teams Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. [![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) From 48375e002175c6a504fa048ff8d46bb2e2322154 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Tue, 18 Oct 2022 21:38:04 -0500 Subject: [PATCH 485/729] Guidance on naming for DateTime values deferred to a future PR --- azure/ConsiderationsForServiceDesign.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 05fcd457..7ce0934d 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -116,8 +116,6 @@ For example, `collectedItems` not `itemsCollected` For example, `nextUrl` not `nextURL`. -:ballot_box_with_check: **YOU SHOULD** use "DateTime" suffix in names of `date-time` values. - :ballot_box_with_check: **YOU SHOULD** use a suffix of the unit of measurement for values with a clear unit of measurement (such as bytes, miles, and so on). Use a generally accepted abbreviation for the units (e.g. "Km" rather than "Kilometers") when appropriate. :ballot_box_with_check: **YOU SHOULD** use an int for time durations and include the time units in the name. From 86603aaa84f674ff402941d857cb7d2226b04b5f Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Tue, 18 Oct 2022 22:09:33 -0500 Subject: [PATCH 486/729] Address more PR review comments --- azure/ConsiderationsForServiceDesign.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 7ce0934d..5e0a30a0 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -89,8 +89,8 @@ As you build out your service and API, there are a number of decisions that can Good names for resources, properties, operations, and parameters are essential to a great developer experience. Resources are described by nouns. Resource and property names must be descriptive and easy for customers to understand. -Avoid names that refer to service implementation details, e.g. "TreeLeafNode", or simply describe the structure of value without -describing its purpose, e.g. "KeyValuePair". +Use names that correspond to user scenarios rather than service implementation details, e.g. "Diagnosis" and not "TreeLeafNode". +Names should convey the value's purpose and not simply describe its structure, e.g. "ConfigurationSetting" and not "KeyValuePair". Ease of understanding comes from familiarity and recognition; you should favor consistency with other Azure services, names in the product's portal/user interface, and industry standards. Names should aid developers in discovering functionality without having to constantly refer to documentation. @@ -128,7 +128,7 @@ For example, `expirationDays` as `int` and not `expiration` as `date-time`. :warning: **YOU SHOULD NOT** use names that are reserved words in widely used programming languages (including C#, Java, JavaScript/TypeScript, Python, C++, and Go). -:no_entry: **DO NOT** use "is" prefix in names of `boolean` values. +:no_entry: **DO NOT** use "is" prefix in names of `boolean` values, e.g. "enabled" not "isEnabled". :no_entry: **DO NOT** use redundant words in names. @@ -167,7 +167,7 @@ Before releasing your API plan to invest significant design effort, get customer As your service evolves over time, it will be natural that you want to remove operations that are no longer needed. For example, additional requirements or new capability in your service, may have resulted in a new operation that, effectively, replaces an old one. Azure has a well established breaking changes policy that describes how to approach these kinds of changes. As part of this policy, the service team is required to clearly communicate to customers when their API is changing, e.g. deprecating operations. Often, this is done via an email to the address that is attached to the Azure subscription. -However, given how many organizations are structured, it's common that this email address is different from the actual people writing code against your API. To address this, the service API should declare that it may return the `azure-deprecating` header, to indicate that this operation will be removed in the future. There is a simple string convention, specied in the [Azure REST API Guidelines](https://aka.ms/azapi/guidelines) that provides more information about the forthcoming deprecation. +However, given how many organizations are structured, it's common that this email address is different from the actual people writing code against your API. To address this, the service API should declare that it may return the `azure-deprecating` header, to indicate that this operation will be removed in the future. There is a simple string convention, specifed in the [Azure REST API Guidelines](https://aka.ms/azapi/guidelines) that provides more information about the forthcoming deprecation. This header is targeted at developers or operation professionals, and it is intended to give them enough information and lead time to properly adapt to this change. Your documentation should reference this header and encourage logging and alerting practices based on its presence. ## Avoid Surprises From ac2d919503617f8086b9ddb070a49360d57d1479 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 27 Oct 2022 09:48:38 -0500 Subject: [PATCH 487/729] Fix anchor tag --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index bf73dd12..edc69c0f 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -636,7 +636,7 @@ Note: The service is responsible for performing any URL-encoding required on the :no_entry: **DO NOT** return the `nextLink` field with a value of null. - :warning: **YOU SHOULD NOT** return a `count` of all objects in the collection as this may be expensive to compute. #### Query options From 17e995c9027cb8a39247399b73f165acece1eec4 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 30 Oct 2022 17:42:44 -0500 Subject: [PATCH 488/729] Add naming guidance for date-time values --- azure/ConsiderationsForServiceDesign.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 5e0a30a0..1b8842f7 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -116,6 +116,8 @@ For example, `collectedItems` not `itemsCollected` For example, `nextUrl` not `nextURL`. +:ballot_box_with_check: **YOU SHOULD** use "DateTime" suffix in names of `date-time` values. + :ballot_box_with_check: **YOU SHOULD** use a suffix of the unit of measurement for values with a clear unit of measurement (such as bytes, miles, and so on). Use a generally accepted abbreviation for the units (e.g. "Km" rather than "Kilometers") when appropriate. :ballot_box_with_check: **YOU SHOULD** use an int for time durations and include the time units in the name. From 37dbe9f9ad0f9a4193a3ca98f3d58cf11fcb6b74 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 4 Nov 2022 13:42:32 -0700 Subject: [PATCH 489/729] Delete change-tracking.md --- graph/patterns/change-tracking.md | 110 ------------------------------ 1 file changed, 110 deletions(-) delete mode 100644 graph/patterns/change-tracking.md diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md deleted file mode 100644 index 2ac39056..00000000 --- a/graph/patterns/change-tracking.md +++ /dev/null @@ -1,110 +0,0 @@ -# Change tracking - -Microsoft Graph API Design Pattern - -*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.* - -## Problem - -API consumers require an efficient way to keep data in sync with Microsoft Graph. The API design should be optimized to avoid polling because it is costly for consumers and producers alike and wouldn't guarantee data integrity. - -## Solution - -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. - -This new endpoint can be used to sync API consumers. This is achieved through returning a delta link with a watermark. After the API consumer refreshes 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. - -> **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. - -## When to use this pattern - -Before using the change tracking pattern in your API definition, make sure that your scenario fits the following criteria: - -- API consumers want to sync the data. -- API consumers don't want to be immediately notified of changes (see the change notifications pattern for this scenario). -- API consumers are not looking for a "one-time" export or back-up mechanism. - -### Alternatives - -- Change notifications pattern (TODO add link when described) -- Backup pattern (TODO) - -## Issues and considerations - -The implementer MUST implement a watermark storage system in case of active watermarks. Passive watermarks are watermarks that can be retrieved from the context (for example, timestamp). Active watermarks represent information required to track the sync state, which cannot be retrieved from the context (such as a cursor from data store, partition affinity marker, partition ID, or generated unique sync identifier). - -The implementer MUST implement soft deletion for entities in the backend storage system. The soft deletion provides useful information to the client to appropriately reflect deletions. - -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. - -- The reason MUST be set to `changed` if the entity can be restored: `"@removed": {"reason": "changed"}` -- The reason MUST be set to `deleted` if the entity cannot be restored: `"@removed": {"reason": "deleted"}` - -When a link to an entity is deleted, when the linked entity is deleted, or when a link to an entity is added, the implementer MUST return a `property@delta` annotation. For example, considering the entity Group has a navigation property named members of type Collection(user): - -- When a user is added to the group `"members@delta": [{ "@odata.type": "#microsoft.graph.user", "id of the added user"}]` -- When a user is removed from the group, or the target user is deleted from `"members@delta": [{"@removed": {"reason": "deleted"}, "id of the deleted or removed user"}]` - -> **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 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. However, expanding across workloads is not currently supported. - -## Examples - -### Get changes for the users entity set - -```HTTP -GET https://graph.microsoft.com/v1.0/users/delta -``` - -```json -{ - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users", - "@odata.deltaLink": "https://graph.microsoft.com/v1.0/users/delta?$deltatoken=mS5DuRZGjVL-abreviated", - "value": [ - { - "businessPhones": [ - "+1 309 555 0104" - ], - "displayName": "Grady Archie", - "givenName": "Grady", - "jobTitle": "Designer", - "mail": "GradyA@contoso.onmicrosoft.com", - "officeLocation": "19/2109", - "preferredLanguage": "en-US", - "surname": "Archie", - "userPrincipalName": "GradyA@contoso.onmicrosoft.com", - "id": "0baaae0f-b0b3-4645-867d-742d8fb669a2", - "manager@delta": [ - { - "@odata.type": "#microsoft.graph.user", - "id": "99789584-a1e1-4232-90e5-866170e3d4e7" - } - ] - }, - { - "id": "0bbbbb0f-b0b3-4645-867d-742d8fb669a2", - "@removed": { - "reason": "changed" - } - } - ] -} -``` - -> **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 that the application can request right away to get the next page. - -### CSDL example - -```xml - - - - - - - - - - - - -``` From 4679cf0278715b86b049c59d7fcddb76d4af81b6 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 15 Nov 2022 09:56:43 -0500 Subject: [PATCH 490/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e1368dd6..33c88039 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -166,7 +166,8 @@ Microsoft Graph APIs should support basic query options in conformance with ODat |Requirements | |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support `$select on resource` to enable properties projection. | -| :ballot_box_with_check: **SHOULD** support `\$filter with eq`, `ne` operations on properties of entities for collections. | +| :ballot_box_with_check: **SHOULD** support `$expand=navProp1` option for navigation properties of entities. | +| :ballot_box_with_check: **SHOULD** support `$filter with eq`, `ne` operations on properties of entities for collections. | | :heavy_check_mark: **MUST** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | | :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip` (or `$skipToken`). | | :ballot_box_with_check: **SHOULD** support `$count` for collections. | @@ -184,7 +185,7 @@ You can model structured resources for your APIs by using the OData entity type Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must not use complex type to model addressable resources such as individually addressable items within a collection. For more information, see the [Microsoft REST API Guidelines collection URL patterns](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. ```xml - + @@ -192,7 +193,7 @@ Because objects of complex types in Microsoft Graph don’t have unique identifi - + @@ -273,11 +274,11 @@ Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs shoul ```http { "error": { - "code": "BadRequest", + "code": "badRequest", "message": "Cannot process the request because a required field is missing.", "target": "query", "innererror": { - "code": "RequiredFieldMissing", + "code": "requiredFieldMissing", } } @@ -291,9 +292,9 @@ The top-level error code must be aligned with HTTP response status codes accordi ```http { "error": { - "code": "BadRequest", + "code": "badRequest", "message": "Cannot process the request because it is malformed or incorrect.", - "target": "Resource X (Optional)" + "target": "resource X (Optional)" } } ``` @@ -303,7 +304,7 @@ The top-level error code must be aligned with HTTP response status codes accordi ```http { "error": { - "code": "BadRequest", + "code": "badRequest", "message": "Cannot process the request because it is malformed or incorrect.", "innererror": { "code": "requiredFieldOrParameterMissing", From 55e8f20e0e76ddebe4365114805fb992e6d2242c Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 15 Nov 2022 18:30:04 -0500 Subject: [PATCH 491/729] Update Guidelines.md Added link to ARM guidelines to help avoid confusion for teams who come here when they are implementing ARM APIs. --- azure/Guidelines.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index bf73dd12..b5ba76cd 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -26,7 +26,7 @@ Please ensure that you add an anchor tag to any new guidelines that you add and ## Introduction -These guidelines offer prescriptive guidance that Azure service teams MUST follow ensuring that customers have a great experience by designing APIs meeting these goals: +These guidelines apply to Azure service teams implementing _data plane_ APIs. They offer prescriptive guidance that Azure service teams MUST follow ensuring that customers have a great experience by designing APIs meeting these goals: - Developer friendly via consistent patterns & web standards (HTTP, REST, JSON) - Efficient & cost-effective - Work well with SDKs in many programming languages @@ -34,12 +34,9 @@ These guidelines offer prescriptive guidance that Azure service teams MUST follo - Sustainable & versionable via clear API contracts with 2 requirements: 1. Customer workloads must never break due to a service change 2. Customers can adopt a version without requiring code changes +Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. Please read the [Considerations for Service Design](./ConsiderationsForServiceDesign.md) for an introduction to the topic of API design for Azure services. *For an existing GA'd service, don't change/break its existing API; instead, leverage these concepts for future APIs while prioritizing consistency within your existing service.* -Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. - -See the [Considerations for Service Design](./ConsiderationsForServiceDesign.md) for an introduction to the topic of API design for Azure services. - -*NOTE: For an existing GA'd service, don't change/break its existing API; instead, leverage these concepts for future APIs while prioritizing consistency within your existing service.* +*Note: If you are creating a management planen (ARM) API, please refer to the [Azure Resource Manager Resource Provider Contract](https://github.com/Azure/azure-resource-manager-rpc).* ### Prescriptive Guidance This document offers prescriptive guidance labeled as follows: From 102e8d35a2d4f3168f231787faaae637c8d126d1 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 15 Nov 2022 18:41:35 -0500 Subject: [PATCH 492/729] Update Guidelines.md fixed typo --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index b5ba76cd..14736bd6 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -36,7 +36,7 @@ These guidelines apply to Azure service teams implementing _data plane_ APIs. Th 2. Customers can adopt a version without requiring code changes Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. Please read the [Considerations for Service Design](./ConsiderationsForServiceDesign.md) for an introduction to the topic of API design for Azure services. *For an existing GA'd service, don't change/break its existing API; instead, leverage these concepts for future APIs while prioritizing consistency within your existing service.* -*Note: If you are creating a management planen (ARM) API, please refer to the [Azure Resource Manager Resource Provider Contract](https://github.com/Azure/azure-resource-manager-rpc).* +*Note: If you are creating a management plane (ARM) API, please refer to the [Azure Resource Manager Resource Provider Contract](https://github.com/Azure/azure-resource-manager-rpc).* ### Prescriptive Guidance This document offers prescriptive guidance labeled as follows: From 5a6465db7d9f9f89a2fd2981b26103269d893263 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 16 Nov 2022 11:39:04 -0500 Subject: [PATCH 493/729] Update change-notification.md --- graph/patterns/change-notification.md | 140 ++++++++++++++++++++++++-- 1 file changed, 130 insertions(+), 10 deletions(-) diff --git a/graph/patterns/change-notification.md b/graph/patterns/change-notification.md index 09f6a814..4732cdc0 100644 --- a/graph/patterns/change-notification.md +++ b/graph/patterns/change-notification.md @@ -2,29 +2,149 @@ Microsoft Graph API Design Pattern -*Provide a short description of the pattern.* +*The change tracking pattern provides the ability for API consumers to request changes in data from Microsoft Graph without having to re-read data that has not changed.* ## Problem -*Describe the business context relevant for the pattern.* - -*Provide a short description of the problem.* +API consumers require an efficient way to acquire changes to data in Microsoft Graph, for example to synchronize an external store or to drive a change-centric business process. ## Solution -*Describe how to implement the solution to solve the problem.* +API designers can enable the change tracking (delta) capability on a resource in the Graph (typically on an entity collection or a parent resource) by declaring a delta function on that resource. -*Describe related patterns.* +This function returns a delta payload. A delta payload consists of a collection of annotated full or partial entities in standard Graph format plus either a nextLink to further pages of original or change data that are immediately available OR a deltaLink to poll to get the next set of changes as they occur. +Annotations allow the delta payload to indicate resources or links which have been deleted. API callers are expected to differentiate resource adds from updates by interpreting the id property of the change records against the existence of resources in whatever external system is doing the processing. ## When to use this pattern -*Describe when and why the solution is applicable and when it might not be.* + API consumers want a pull mechanism to request and process change to Graph data, either via proactive polling or by responding to Graph notifications. +API producers can provide a low data latency (sub-minute at P95). +When API consumers need guaranteed data integrity over the set of changes to Graph data. ## Issues and considerations -*Describe tradeoffs of the solution.* +- The pattern requires a sequence of requests. + + 1. GET request on the delta function, which returns the first page of the current state of the resources that delta applies to. + 2. [Optionally] Further GET requests to retrieve more pages of the current state via the odata.nextLink. + 3. After some time, a GET request to see if there are new changes via the odata.deltaLink. + 4. [Optionally] GET requests to retrieve more pages of changes via the odata.nextLink. + + API producers MAY respond to standard Graph OData query parameters with the initial call to the delta function: + + - `$select` to enforce the set of properties on which change is reported. + - `$filter` to influence the range of changes returned. + - `$expand` to include linked resources with the set of changes. + - `$top` parameter to influence the size of the set of change records. + + These query parameters MUST be encoded into subsequent odata.nextLink or odata.deltaLink, such that the same options are preserved through the call sequence without callers respecifying them, which MUST NOT be allowed. + + Making a sequence of calls to a delta function followed by the opaque urls in the “nextLink” and “deltaLink” MUST guarantee that the data at the start time of the call sequence and all changes to the data thereafter will be returned at least once. It is not necessary to avoid duplicates in the sequence. When the delta function is returning changes, they MUST be sequenced chronologically refer to [public documentation](https://learn.microsoft.com/en-us/graph/delta-query-overview?view=graph-rest-1.0) for more details. + +- The delta function can be bound to a collection, as with +`/users/delta` – this returns the changes to the users' collection. + + or to some logical parent resource, where the change records are implied to be relative to all collections contained within the parent, for example + `/me/planner/all/delta` – this returns changes to any resource within planner that a user is subscribed to as a heterogenous collection. +- Delta payload requirements [OData spec](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_DeltaPayloadResponse) + - The payload is a collection of change records using the standard Graph collection format. + - The change records are full or partial representations of the resources that changed using the standard Graph types for the resources. + - When a change representing a resource update is included in the payload the API producer MAY return either the changed properties or the full entity. The id of the resource MUST be included in every change record. - The nextLink provides a mechanism to do server-driven paging through the change data that is currently available. When there are no further pages of changes immediately available, a deltaLink is returned instead. + - The deltaLink provides a mechanism for the API consumer to catch up on changes since their last request to the delta function, deltaLink or last page’s nextLink. If no changes have happened since the last request, then the deltaLink MUST return an empty collection. + - Both “nextLink” and “deltaLink” MUST be considered opaque URLs. The best practice is to make them opaque via encoding. + - When an entity is deleted, the delta function MUST return the ID of the deleted entity as well as an @removed annotation with the reason field. + - When an entity is deleted, the reason MUST be set to “changed” if the entity can be restored. + - When an entity is deleted. the reason MUST be set to “deleted” if the entity cannot be restored. + - There is no mechanism to indicate that a resource has entered or exited the dataset based on a change that causes it to match or no longer match any $filter query parameter. + - When a link to an entity is deleted, when the linked entity is deleted, or when a link to an entity is added, the implementer MUST return a “property@delta” annotation. + - When a link to an entity is deleted, but the entity still exists, the reason MUST be set to “changed”. + - When a link to an entity is deleted along with the entity, the reason MUST be set to “deleted”. + - OData query parameters must be honored in full, or a 400-error returned. + +## Alternatives ## + +- Change notifications pattern with rich payloads – for use cases where API consumers would find calling back into Graph onerous and absolute integrity guarantees are less critical. + + +## Examples + +### Delta payload + + Here, a user resource is updated, and there is one user added to and one removed from that user’s directReports collection. Additionally, a second user is deleted. In this case, there are no further pages of change records currently available. + +``` +GET https://graph.microsoft.com/v1.0/users/delta + +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users", + "@odata.deltaLink": "https://graph.microsoft.com/v1.0/users/delta?$deltatoken=mS5DuRZGjVL-abreviated", + "value": [ + { + "businessPhones": ["+1 309 555 0104"], + "displayName": "Grady Archie", + "givenName": "Grady", + "jobTitle": "Designer", + "mail": "GradyA@contoso.onmicrosoft.com", + "officeLocation": "19/2109", + "preferredLanguage": "en-US", + "surname": "Archie", + "userPrincipalName": "GradyA@contoso.onmicrosoft.com", + "id": "0baaae0f-b0b3-4645-867d-742d8fb669a2", + "directReports@delta": [ + { + "@odata.type": "#microsoft.graph.user", + "id": "99789584-a1e1-4232-90e5-866170e3d4e7" + } , + { + "id": "66789583-f1e2-6232-70e5-366170e3d4a6", + "@removed": { + "reason": "deleted" + } + } + ] + }, + { + "id": "0bbbbb0f-b0b3-4645-867d-742d8fb669a2", + "@removed": { + "reason": "changed" + } + } + ] +} +``` + +### API producer CSDL examples + +EntitySet example +``` + + + + + + + + + + + +``` -## Example +NavigationProperty example -*Provide a short example from real life.* \ No newline at end of file +``` + + + + + + + + + + + + + +``` \ No newline at end of file From 15844d087cd095a41a4bd48c46b21a652248eca0 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 16 Nov 2022 15:07:03 -0500 Subject: [PATCH 494/729] fixed pattern name --- graph/patterns/{change-notification.md => change-tracking.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename graph/patterns/{change-notification.md => change-tracking.md} (98%) diff --git a/graph/patterns/change-notification.md b/graph/patterns/change-tracking.md similarity index 98% rename from graph/patterns/change-notification.md rename to graph/patterns/change-tracking.md index 4732cdc0..1de46a5e 100644 --- a/graph/patterns/change-notification.md +++ b/graph/patterns/change-tracking.md @@ -1,4 +1,4 @@ -# Change notification +# Change tracking Microsoft Graph API Design Pattern @@ -47,7 +47,7 @@ When API consumers need guaranteed data integrity over the set of changes to Gra or to some logical parent resource, where the change records are implied to be relative to all collections contained within the parent, for example `/me/planner/all/delta` – this returns changes to any resource within planner that a user is subscribed to as a heterogenous collection. -- Delta payload requirements [OData spec](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_DeltaPayloadResponse) +- Delta payload requirements - The payload is a collection of change records using the standard Graph collection format. - The change records are full or partial representations of the resources that changed using the standard Graph types for the resources. - When a change representing a resource update is included in the payload the API producer MAY return either the changed properties or the full entity. The id of the resource MUST be included in every change record. - The nextLink provides a mechanism to do server-driven paging through the change data that is currently available. When there are no further pages of changes immediately available, a deltaLink is returned instead. From 7380cc34d02fdbda8bbdf69bfc0e697bf47e883e Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 17 Nov 2022 19:06:11 -0500 Subject: [PATCH 495/729] editing for clarity --- graph/patterns/change-tracking.md | 155 ++++++++++++++++-------------- 1 file changed, 83 insertions(+), 72 deletions(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 1de46a5e..492df721 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -7,69 +7,115 @@ Microsoft Graph API Design Pattern ## Problem -API consumers require an efficient way to acquire changes to data in Microsoft Graph, for example to synchronize an external store or to drive a change-centric business process. +API consumers require an efficient way to acquire changes to data in the Microsoft Graph, for example to synchronize an external store or to drive a change-centric business process. ## Solution -API designers can enable the change tracking (delta) capability on a resource in the Graph (typically on an entity collection or a parent resource) by declaring a delta function on that resource. +API designers can enable the change tracking (delta) capability on a resource in the Microsoft Graph (typically on an entity collection or a parent resource) by declaring a delta function on that resource. + +This function returns a delta payload. A delta payload consists of a collection of annotated full or partial Microsoft Graph entities plus either a `nextLink` to further pages of original or change data that are immediately available OR a `deltaLink` to poll to get the next set of changes as they occur. -This function returns a delta payload. A delta payload consists of a collection of annotated full or partial entities in standard Graph format plus either a nextLink to further pages of original or change data that are immediately available OR a deltaLink to poll to get the next set of changes as they occur. Annotations allow the delta payload to indicate resources or links which have been deleted. API callers are expected to differentiate resource adds from updates by interpreting the id property of the change records against the existence of resources in whatever external system is doing the processing. -## When to use this pattern +The pattern requires a sequence of requests. + + 1. GET request on the delta function, which returns the first page of the current state of the resources that delta applies to. + 2. [Optionally] Further GET requests to retrieve more pages of the current state via the `@odata.nextLink` URL. + 3. After some time, a GET request to see if there are new changes via the `@odata.deltaLink`URL. + 4. [Optionally] GET requests to retrieve more pages of changes via the `@odata.nextLink` URL. - API consumers want a pull mechanism to request and process change to Graph data, either via proactive polling or by responding to Graph notifications. -API producers can provide a low data latency (sub-minute at P95). -When API consumers need guaranteed data integrity over the set of changes to Graph data. +The `nextLink` provides a mechanism to do server-driven paging through the change data that is currently available. When there are no further pages of changes immediately available, a `deltaLink` is returned instead. -## Issues and considerations +The `deltaLink` provides a mechanism for the API consumer to catch up on changes since their last request to the delta function, `deltaLink` or last page’s `nextLink`. If no changes have happened since the last request, then the deltaLink MUST return an empty collection. -- The pattern requires a sequence of requests. +Both `nextLink` and `deltaLink` MUST be considered opaque URLs. The best practice is to make them opaque via encoding. - 1. GET request on the delta function, which returns the first page of the current state of the resources that delta applies to. - 2. [Optionally] Further GET requests to retrieve more pages of the current state via the odata.nextLink. - 3. After some time, a GET request to see if there are new changes via the odata.deltaLink. - 4. [Optionally] GET requests to retrieve more pages of changes via the odata.nextLink. +Delta payload requirements + - The payload is a collection of change records using the collection format. + - The change records are full or partial representations of the resources according to their resource types. + - When a change representing a resource update is included in the payload the API producer MAY return either the changed properties or the full entity. The ID of the resource MUST be included in every change record. + - When an entity is deleted, the delta function MUST return the ID of the deleted entity as well as an `@removed` annotation with the reason field. + - When an entity is deleted, the reason MUST be set to “changed” if the entity can be restored. + - When an entity is deleted. the reason MUST be set to “deleted” if the entity cannot be restored. + - There is no mechanism to indicate that a resource has entered or exited the dataset based on a change that causes it to match or no longer match any `$filter` query parameter. + - When a link to an entity is deleted, when the linked entity is deleted, or when a link to an entity is added, the implementer MUST return a `property@delta` annotation. + - When a link to an entity is deleted, but the entity still exists, the reason MUST be set to `changed`. + - When a link to an entity is deleted along with the entity, the reason MUST be set to `deleted`. + +API producers MAY choose to collate multiple changes to the same resource into a single change record. + +## When to use this pattern - API producers MAY respond to standard Graph OData query parameters with the initial call to the delta function: +API consumers want a pull mechanism to request and process change to Microsoft Graph data, either via proactive polling or by responding to Microsoft Graph notifications. + +API consumers need guaranteed data integrity over the set of changes to Microsoft Graph data. + +## Issues and considerations + + - API service MAY be able to respond to standard OData query parameters with the initial call to the delta function: - `$select` to enforce the set of properties on which change is reported. - `$filter` to influence the range of changes returned. - `$expand` to include linked resources with the set of changes. - `$top` parameter to influence the size of the set of change records. - These query parameters MUST be encoded into subsequent odata.nextLink or odata.deltaLink, such that the same options are preserved through the call sequence without callers respecifying them, which MUST NOT be allowed. + These query parameters MUST be encoded into subsequent `@odata.nextLink` or `@odata.deltaLink`, such that the same options are preserved through the call sequence without callers respecifying them, which MUST NOT be allowed. OData query parameters must be honored in full, or a 400-error returned. - Making a sequence of calls to a delta function followed by the opaque urls in the “nextLink” and “deltaLink” MUST guarantee that the data at the start time of the call sequence and all changes to the data thereafter will be returned at least once. It is not necessary to avoid duplicates in the sequence. When the delta function is returning changes, they MUST be sequenced chronologically refer to [public documentation](https://learn.microsoft.com/en-us/graph/delta-query-overview?view=graph-rest-1.0) for more details. +- Making a sequence of calls to a delta function followed by the opaque URLs in the `nextLink` and `deltaLink` MUST guarantee that the data at the start time of the call sequence and all changes to the data thereafter will be returned at least once. It is not necessary to avoid duplicates in the sequence. When the delta function is returning changes, they MUST be sequenced chronologically refer to [public documentation](https://learn.microsoft.com/en-us/graph/delta-query-overview?view=graph-rest-1.0) for more details. - The delta function can be bound to a collection, as with -`/users/delta` – this returns the changes to the users' collection. - - or to some logical parent resource, where the change records are implied to be relative to all collections contained within the parent, for example - `/me/planner/all/delta` – this returns changes to any resource within planner that a user is subscribed to as a heterogenous collection. -- Delta payload requirements - - The payload is a collection of change records using the standard Graph collection format. - - The change records are full or partial representations of the resources that changed using the standard Graph types for the resources. - - When a change representing a resource update is included in the payload the API producer MAY return either the changed properties or the full entity. The id of the resource MUST be included in every change record. - The nextLink provides a mechanism to do server-driven paging through the change data that is currently available. When there are no further pages of changes immediately available, a deltaLink is returned instead. - - The deltaLink provides a mechanism for the API consumer to catch up on changes since their last request to the delta function, deltaLink or last page’s nextLink. If no changes have happened since the last request, then the deltaLink MUST return an empty collection. - - Both “nextLink” and “deltaLink” MUST be considered opaque URLs. The best practice is to make them opaque via encoding. - - When an entity is deleted, the delta function MUST return the ID of the deleted entity as well as an @removed annotation with the reason field. - - When an entity is deleted, the reason MUST be set to “changed” if the entity can be restored. - - When an entity is deleted. the reason MUST be set to “deleted” if the entity cannot be restored. - - There is no mechanism to indicate that a resource has entered or exited the dataset based on a change that causes it to match or no longer match any $filter query parameter. - - When a link to an entity is deleted, when the linked entity is deleted, or when a link to an entity is added, the implementer MUST return a “property@delta” annotation. - - When a link to an entity is deleted, but the entity still exists, the reason MUST be set to “changed”. - - When a link to an entity is deleted along with the entity, the reason MUST be set to “deleted”. - - OData query parameters must be honored in full, or a 400-error returned. +`/users/delta` that returns the changes to the users' collection. + + or to some logical parent resource, where the change records are implied to be relative to all collections contained within the parent, for example + `/me/planner/all/delta` – this returns changes to any resource within planner that a user is subscribed to as a heterogenous collection. -## Alternatives ## +- Although this capability is similar to the OData `$delta` feed 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. +- API producers might use `$skipToken` and `$deltaToken` within their implementations of `nextLink` and `deltaLink`, however the URLs are defined as being opaque and the existence of the tokens MUST NOT be documented. It is not a breaking change to modify the structure of `nextLinks` or `deltaLinks`.- +- `nextLink` and `deltaLink` URLs are valid for a specific period before the client application needs to run a full synchronization again.For `nextLink`, a minimal validity time should be 1 hour. For `deltaLink`, a minimal validity time should be seven days. When a link is no longer valid it must return a standard error with a 410 GONE response code. + -- Change notifications pattern with rich payloads – for use cases where API consumers would find calling back into Graph onerous and absolute integrity guarantees are less critical. +## Alternatives + +- Change notifications pattern with rich payloads – for use cases where API consumers would find calling back into Microsoft Graph onerous and absolute integrity guarantees are less critical. ## Examples -### Delta payload +### Change tracking on entity set + +``` + + + + + + + + + + + +``` + +### Change tracking on navigation property + +``` + + + + + + + + + + + + + +``` + +### Delta payload Here, a user resource is updated, and there is one user added to and one removed from that user’s directReports collection. Additionally, a second user is deleted. In this case, there are no further pages of change records currently available. @@ -112,39 +158,4 @@ GET https://graph.microsoft.com/v1.0/users/delta } ] } -``` - -### API producer CSDL examples - -EntitySet example -``` - - - - - - - - - - - -``` - -NavigationProperty example - -``` - - - - - - - - - - - - - ``` \ No newline at end of file From 4369f57692f0430a0dd4972ee5e39ea860b218c1 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 29 Nov 2022 16:19:26 -0500 Subject: [PATCH 496/729] Update graph/GuidelinesGraph.md Co-authored-by: Dan Kershaw [MSFT] --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 33c88039..90e22637 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -167,7 +167,7 @@ Microsoft Graph APIs should support basic query options in conformance with ODat |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support `$select on resource` to enable properties projection. | | :ballot_box_with_check: **SHOULD** support `$expand=navProp1` option for navigation properties of entities. | -| :ballot_box_with_check: **SHOULD** support `$filter with eq`, `ne` operations on properties of entities for collections. | +| :ballot_box_with_check: **SHOULD** support `$filter` with `eq` and `ne` operations on properties of entity collections. | | :heavy_check_mark: **MUST** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | | :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip` (or `$skipToken`). | | :ballot_box_with_check: **SHOULD** support `$count` for collections. | From 5558b8dc26f0b4febe4552af6d4b0acd9c03a316 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 29 Nov 2022 16:20:28 -0500 Subject: [PATCH 497/729] Update graph/GuidelinesGraph.md Co-authored-by: Dan Kershaw [MSFT] --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 90e22637..79b4425f 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -182,7 +182,7 @@ Another way to avoid this is to use JSON batch as described in the [Microsoft Gr You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type does not. In Microsoft Graph, this key property is called `id` for server-created key values. If there is a natural name for the key property, then the workload can use that. -Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must not use complex type to model addressable resources such as individually addressable items within a collection. For more information, see the [Microsoft REST API Guidelines collection URL patterns](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. +Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must use entity types to model addressable resources such as individually addressable items within a collection. For more information, see the [Microsoft REST API Guidelines collection URL patterns](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. ```xml From 76e1da07e7dbaa3c07d9ff5f0eeaf39e66d89bd2 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 30 Nov 2022 17:49:48 -0500 Subject: [PATCH 498/729] corrected examples --- graph/patterns/change-tracking.md | 47 +++++++++++++++++-------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 492df721..6de0d08b 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -11,7 +11,7 @@ API consumers require an efficient way to acquire changes to data in the Microso ## Solution -API designers can enable the change tracking (delta) capability on a resource in the Microsoft Graph (typically on an entity collection or a parent resource) by declaring a delta function on that resource. +API designers can enable the change tracking (delta) capability on a resource in the Microsoft Graph (typically on an entity collection or a parent resource) by declaring a delta function on that resource and applying `Org.OData.Capabilities.V1.ChangeTracking` annotation. This function returns a delta payload. A delta payload consists of a collection of annotated full or partial Microsoft Graph entities plus either a `nextLink` to further pages of original or change data that are immediately available OR a `deltaLink` to poll to get the next set of changes as they occur. @@ -39,8 +39,8 @@ Delta payload requirements - When an entity is deleted. the reason MUST be set to “deleted” if the entity cannot be restored. - There is no mechanism to indicate that a resource has entered or exited the dataset based on a change that causes it to match or no longer match any `$filter` query parameter. - When a link to an entity is deleted, when the linked entity is deleted, or when a link to an entity is added, the implementer MUST return a `property@delta` annotation. - - When a link to an entity is deleted, but the entity still exists, the reason MUST be set to `changed`. - - When a link to an entity is deleted along with the entity, the reason MUST be set to `deleted`. + - When a link to an entity is deleted, but the entity still exists, the reason MUST be set to `changed`. + - When a link to an entity is deleted along with the entity, the reason MUST be set to `deleted`. API producers MAY choose to collate multiple changes to the same resource into a single change record. @@ -84,10 +84,10 @@ API consumers need guaranteed data integrity over the set of changes to Microsof ### Change tracking on entity set ``` - - - - + + + + @@ -100,27 +100,32 @@ API consumers need guaranteed data integrity over the set of changes to Microsof ### Change tracking on navigation property ``` - - - - - - - - - - - - + + + + + + + + + + + + + + + + + ``` ### Delta payload - Here, a user resource is updated, and there is one user added to and one removed from that user’s directReports collection. Additionally, a second user is deleted. In this case, there are no further pages of change records currently available. + Here, a user resource is updated, and there is one user added to and one removed from that user’s directReports collection. Additionally, a second user is deleted. In this case, there are no further pages of change records currently available. For detailed sequence of requests see [public documentation](https://learn.microsoft.com/en-us/graph/delta-query-users?tabs=http). ``` -GET https://graph.microsoft.com/v1.0/users/delta +GET https://graph.microsoft.com/v1.0/users/delta?$skiptoken=pqwSUjGYvb3jQpbwVAwEL7yuI3dU1LecfkkfLPtnIjvB7XnF_yllFsCrZJ { "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users", From 1b204676a93de785c958e0f0a6136a984f147ef6 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 30 Nov 2022 18:23:03 -0500 Subject: [PATCH 499/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 79b4425f..7c1b6db3 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -166,7 +166,7 @@ Microsoft Graph APIs should support basic query options in conformance with ODat |Requirements | |----------------------------------------------------------------------------------------------------| | :heavy_check_mark: **MUST** support `$select on resource` to enable properties projection. | -| :ballot_box_with_check: **SHOULD** support `$expand=navProp1` option for navigation properties of entities. | +| :ballot_box_with_check: **SHOULD** support `/entityTypeCollection/{id}?$expand=navProp1` option for navigation properties of entities. | | :ballot_box_with_check: **SHOULD** support `$filter` with `eq` and `ne` operations on properties of entity collections. | | :heavy_check_mark: **MUST** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | | :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip` (or `$skipToken`). | From 98858bde512a9c1a68251764f6891ae7fca35372 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 30 Nov 2022 18:37:02 -0500 Subject: [PATCH 500/729] updated sequence --- graph/patterns/change-tracking.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 6de0d08b..6dc85ad0 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -17,9 +17,9 @@ This function returns a delta payload. A delta payload consists of a collection Annotations allow the delta payload to indicate resources or links which have been deleted. API callers are expected to differentiate resource adds from updates by interpreting the id property of the change records against the existence of resources in whatever external system is doing the processing. -The pattern requires a sequence of requests. +The pattern requires a sequence of requests on the delta function: - 1. GET request on the delta function, which returns the first page of the current state of the resources that delta applies to. + 1. GET request which returns the first page of the current state of the resources that delta applies to. 2. [Optionally] Further GET requests to retrieve more pages of the current state via the `@odata.nextLink` URL. 3. After some time, a GET request to see if there are new changes via the `@odata.deltaLink`URL. 4. [Optionally] GET requests to retrieve more pages of changes via the `@odata.nextLink` URL. @@ -55,7 +55,7 @@ API consumers need guaranteed data integrity over the set of changes to Microsof - API service MAY be able to respond to standard OData query parameters with the initial call to the delta function: - `$select` to enforce the set of properties on which change is reported. - - `$filter` to influence the range of changes returned. + - `$filter` to influence the scope of changes returned. - `$expand` to include linked resources with the set of changes. - `$top` parameter to influence the size of the set of change records. From 504bedba2d5181bcd3ceafcb491f0ca01b907021 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 30 Nov 2022 18:44:28 -0500 Subject: [PATCH 501/729] added link --- graph/patterns/change-tracking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 6dc85ad0..60aeb9e2 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -17,7 +17,7 @@ This function returns a delta payload. A delta payload consists of a collection Annotations allow the delta payload to indicate resources or links which have been deleted. API callers are expected to differentiate resource adds from updates by interpreting the id property of the change records against the existence of resources in whatever external system is doing the processing. -The pattern requires a sequence of requests on the delta function: +The pattern requires a sequence of requests on the delta function, for additional details see [Change Tracking](https://learn.microsoft.com/en-us/graph/delta-query-overview?tabs=http#use-delta-query-to-track-changes-in-a-resource-collection): 1. GET request which returns the first page of the current state of the resources that delta applies to. 2. [Optionally] Further GET requests to retrieve more pages of the current state via the `@odata.nextLink` URL. From 5df75ae7e1e35992ec8e201b56871af622e2dc03 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 30 Nov 2022 18:48:57 -0500 Subject: [PATCH 502/729] example update --- graph/patterns/change-tracking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 60aeb9e2..98651b28 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -122,7 +122,7 @@ API consumers need guaranteed data integrity over the set of changes to Microsof ### Delta payload - Here, a user resource is updated, and there is one user added to and one removed from that user’s directReports collection. Additionally, a second user is deleted. In this case, there are no further pages of change records currently available. For detailed sequence of requests see [public documentation](https://learn.microsoft.com/en-us/graph/delta-query-users?tabs=http). + Here after the initial delta call, a user resource is updated, and there is one user added to and one removed from that user’s directReports collection. Additionally, a second user is deleted. In this case, there are no further pages of change records currently available. For detailed sequence of requests see [Change Tracking](https://learn.microsoft.com/en-us/graph/delta-query-overview?tabs=http#use-delta-query-to-track-changes-in-a-resource-collection). ``` GET https://graph.microsoft.com/v1.0/users/delta?$skiptoken=pqwSUjGYvb3jQpbwVAwEL7yuI3dU1LecfkkfLPtnIjvB7XnF_yllFsCrZJ From 925b5e40ecbd0a19c56bdc4830bab7baf1b2a85b Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 1 Dec 2022 18:03:26 -0500 Subject: [PATCH 503/729] Update change-tracking.md --- graph/patterns/change-tracking.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 98651b28..979b00cc 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -21,7 +21,7 @@ The pattern requires a sequence of requests on the delta function, for additiona 1. GET request which returns the first page of the current state of the resources that delta applies to. 2. [Optionally] Further GET requests to retrieve more pages of the current state via the `@odata.nextLink` URL. - 3. After some time, a GET request to see if there are new changes via the `@odata.deltaLink`URL. + 3. After some time, a GET request to see if there are new changes via the `@odata.deltaLink` URL. 4. [Optionally] GET requests to retrieve more pages of changes via the `@odata.nextLink` URL. The `nextLink` provides a mechanism to do server-driven paging through the change data that is currently available. When there are no further pages of changes immediately available, a `deltaLink` is returned instead. @@ -30,7 +30,7 @@ The `deltaLink` provides a mechanism for the API consumer to catch up on changes Both `nextLink` and `deltaLink` MUST be considered opaque URLs. The best practice is to make them opaque via encoding. -Delta payload requirements +Delta payload requirements: - The payload is a collection of change records using the collection format. - The change records are full or partial representations of the resources according to their resource types. - When a change representing a resource update is included in the payload the API producer MAY return either the changed properties or the full entity. The ID of the resource MUST be included in every change record. @@ -50,7 +50,7 @@ API consumers want a pull mechanism to request and process change to Microsoft G API consumers need guaranteed data integrity over the set of changes to Microsoft Graph data. -## Issues and considerations +## Considerations - API service MAY be able to respond to standard OData query parameters with the initial call to the delta function: @@ -60,18 +60,15 @@ API consumers need guaranteed data integrity over the set of changes to Microsof - `$top` parameter to influence the size of the set of change records. These query parameters MUST be encoded into subsequent `@odata.nextLink` or `@odata.deltaLink`, such that the same options are preserved through the call sequence without callers respecifying them, which MUST NOT be allowed. OData query parameters must be honored in full, or a 400-error returned. - - Making a sequence of calls to a delta function followed by the opaque URLs in the `nextLink` and `deltaLink` MUST guarantee that the data at the start time of the call sequence and all changes to the data thereafter will be returned at least once. It is not necessary to avoid duplicates in the sequence. When the delta function is returning changes, they MUST be sequenced chronologically refer to [public documentation](https://learn.microsoft.com/en-us/graph/delta-query-overview?view=graph-rest-1.0) for more details. - - The delta function can be bound to a collection, as with -`/users/delta` that returns the changes to the users' collection. - +`/users/delta` that returns the changes to the users' collection or to some logical parent resource, where the change records are implied to be relative to all collections contained within the parent, for example `/me/planner/all/delta` – this returns changes to any resource within planner that a user is subscribed to as a heterogenous collection. - -- Although this capability is similar to the OData `$delta` feed 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. - API producers might use `$skipToken` and `$deltaToken` within their implementations of `nextLink` and `deltaLink`, however the URLs are defined as being opaque and the existence of the tokens MUST NOT be documented. It is not a breaking change to modify the structure of `nextLinks` or `deltaLinks`.- - `nextLink` and `deltaLink` URLs are valid for a specific period before the client application needs to run a full synchronization again.For `nextLink`, a minimal validity time should be 1 hour. For `deltaLink`, a minimal validity time should be seven days. When a link is no longer valid it must return a standard error with a 410 GONE response code. +- Although this capability is similar to the OData `$delta` feed 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. +- The Graph delta payload format has some deviations from the OData 4.01 change tracking format to simplify parsing, for example the context annotation is removed. ## Alternatives From 653bbf9bb19f1801c069d09561eabe922a93427a Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 2 Dec 2022 16:31:41 -0500 Subject: [PATCH 504/729] Update graph/patterns/change-tracking.md Co-authored-by: Dan Kershaw [MSFT] --- graph/patterns/change-tracking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 979b00cc..792584eb 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -13,7 +13,7 @@ API consumers require an efficient way to acquire changes to data in the Microso API designers can enable the change tracking (delta) capability on a resource in the Microsoft Graph (typically on an entity collection or a parent resource) by declaring a delta function on that resource and applying `Org.OData.Capabilities.V1.ChangeTracking` annotation. -This function returns a delta payload. A delta payload consists of a collection of annotated full or partial Microsoft Graph entities plus either a `nextLink` to further pages of original or change data that are immediately available OR a `deltaLink` to poll to get the next set of changes as they occur. +This function returns a delta payload. A delta payload consists of a collection of annotated full or partial Microsoft Graph entities plus either a `nextLink` to further pages of original or change data that are immediately available OR a `deltaLink` to get the next set of changes at some later date. Annotations allow the delta payload to indicate resources or links which have been deleted. API callers are expected to differentiate resource adds from updates by interpreting the id property of the change records against the existence of resources in whatever external system is doing the processing. From 78228421cf91fbd12ce9bc95961dc6ca0e40d71d Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 2 Dec 2022 16:32:52 -0500 Subject: [PATCH 505/729] Update graph/patterns/change-tracking.md Co-authored-by: Dan Kershaw [MSFT] --- graph/patterns/change-tracking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 792584eb..56a96de4 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -26,7 +26,7 @@ The pattern requires a sequence of requests on the delta function, for additiona The `nextLink` provides a mechanism to do server-driven paging through the change data that is currently available. When there are no further pages of changes immediately available, a `deltaLink` is returned instead. -The `deltaLink` provides a mechanism for the API consumer to catch up on changes since their last request to the delta function, `deltaLink` or last page’s `nextLink`. If no changes have happened since the last request, then the deltaLink MUST return an empty collection. +The `deltaLink` provides a mechanism for the API consumer to catch up on changes since their last request to the delta function. If no changes have happened since the last request, then the deltaLink MUST return an empty collection. Both `nextLink` and `deltaLink` MUST be considered opaque URLs. The best practice is to make them opaque via encoding. From 7d22a099446953979fb1c40083f6f0c0f2bd182c Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 2 Dec 2022 17:13:43 -0500 Subject: [PATCH 506/729] public namespace --- graph/patterns/change-tracking.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 56a96de4..0b4613b0 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -15,7 +15,10 @@ API designers can enable the change tracking (delta) capability on a resource in This function returns a delta payload. A delta payload consists of a collection of annotated full or partial Microsoft Graph entities plus either a `nextLink` to further pages of original or change data that are immediately available OR a `deltaLink` to get the next set of changes at some later date. -Annotations allow the delta payload to indicate resources or links which have been deleted. API callers are expected to differentiate resource adds from updates by interpreting the id property of the change records against the existence of resources in whatever external system is doing the processing. +The `nextLink` provides a mechanism to do server-driven paging through the change data that is currently available. When there are no further pages of changes immediately available, a `deltaLink` is returned instead. +The `deltaLink` provides a mechanism for the API consumer to catch up on changes since their last request to the delta function. If no changes have happened since the last request, then the deltaLink MUST return an empty collection. + +Both `nextLink` and `deltaLink` MUST be considered opaque URLs. The best practice is to make them opaque via encoding. The pattern requires a sequence of requests on the delta function, for additional details see [Change Tracking](https://learn.microsoft.com/en-us/graph/delta-query-overview?tabs=http#use-delta-query-to-track-changes-in-a-resource-collection): @@ -24,12 +27,6 @@ The pattern requires a sequence of requests on the delta function, for additiona 3. After some time, a GET request to see if there are new changes via the `@odata.deltaLink` URL. 4. [Optionally] GET requests to retrieve more pages of changes via the `@odata.nextLink` URL. -The `nextLink` provides a mechanism to do server-driven paging through the change data that is currently available. When there are no further pages of changes immediately available, a `deltaLink` is returned instead. - -The `deltaLink` provides a mechanism for the API consumer to catch up on changes since their last request to the delta function. If no changes have happened since the last request, then the deltaLink MUST return an empty collection. - -Both `nextLink` and `deltaLink` MUST be considered opaque URLs. The best practice is to make them opaque via encoding. - Delta payload requirements: - The payload is a collection of change records using the collection format. - The change records are full or partial representations of the resources according to their resource types. @@ -41,9 +38,12 @@ Delta payload requirements: - When a link to an entity is deleted, when the linked entity is deleted, or when a link to an entity is added, the implementer MUST return a `property@delta` annotation. - When a link to an entity is deleted, but the entity still exists, the reason MUST be set to `changed`. - When a link to an entity is deleted along with the entity, the reason MUST be set to `deleted`. - + API producers MAY choose to collate multiple changes to the same resource into a single change record. +API callers are expected to differentiate resource adds from updates by interpreting the id property of the change records against the existence of resources in whatever external system is doing the processing. + + ## When to use this pattern API consumers want a pull mechanism to request and process change to Microsoft Graph data, either via proactive polling or by responding to Microsoft Graph notifications. @@ -65,7 +65,7 @@ API consumers need guaranteed data integrity over the set of changes to Microsof `/users/delta` that returns the changes to the users' collection or to some logical parent resource, where the change records are implied to be relative to all collections contained within the parent, for example `/me/planner/all/delta` – this returns changes to any resource within planner that a user is subscribed to as a heterogenous collection. -- API producers might use `$skipToken` and `$deltaToken` within their implementations of `nextLink` and `deltaLink`, however the URLs are defined as being opaque and the existence of the tokens MUST NOT be documented. It is not a breaking change to modify the structure of `nextLinks` or `deltaLinks`.- +- API producers should use `$skipToken` and `$deltaToken` within their implementations of `nextLink` and `deltaLink`, however the URLs are defined as being opaque and the existence of the tokens MUST NOT be documented. It is not a breaking change to modify the structure of `nextLinks` or `deltaLinks`.- - `nextLink` and `deltaLink` URLs are valid for a specific period before the client application needs to run a full synchronization again.For `nextLink`, a minimal validity time should be 1 hour. For `deltaLink`, a minimal validity time should be seven days. When a link is no longer valid it must return a standard error with a 410 GONE response code. - Although this capability is similar to the OData `$delta` feed 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. - The Graph delta payload format has some deviations from the OData 4.01 change tracking format to simplify parsing, for example the context annotation is removed. @@ -81,11 +81,11 @@ API consumers need guaranteed data integrity over the set of changes to Microsof ### Change tracking on entity set ``` - + - + @@ -97,19 +97,19 @@ API consumers need guaranteed data integrity over the set of changes to Microsof ### Change tracking on navigation property ``` - + - + - + - + From 0984dcbb7b5a478da27aac5b6c15e74a36e97af6 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 2 Dec 2022 18:13:15 -0500 Subject: [PATCH 507/729] Update change-tracking.md --- graph/patterns/change-tracking.md | 48 +++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index 0b4613b0..e94d38cc 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -41,7 +41,7 @@ Delta payload requirements: API producers MAY choose to collate multiple changes to the same resource into a single change record. -API callers are expected to differentiate resource adds from updates by interpreting the id property of the change records against the existence of resources in whatever external system is doing the processing. +API consumers are expected to differentiate resource adds from updates by interpreting the id property of the change records against the existence of resources in whatever external system is doing the processing. ## When to use this pattern @@ -61,14 +61,15 @@ API consumers need guaranteed data integrity over the set of changes to Microsof These query parameters MUST be encoded into subsequent `@odata.nextLink` or `@odata.deltaLink`, such that the same options are preserved through the call sequence without callers respecifying them, which MUST NOT be allowed. OData query parameters must be honored in full, or a 400-error returned. - Making a sequence of calls to a delta function followed by the opaque URLs in the `nextLink` and `deltaLink` MUST guarantee that the data at the start time of the call sequence and all changes to the data thereafter will be returned at least once. It is not necessary to avoid duplicates in the sequence. When the delta function is returning changes, they MUST be sequenced chronologically refer to [public documentation](https://learn.microsoft.com/en-us/graph/delta-query-overview?view=graph-rest-1.0) for more details. -- The delta function can be bound to a collection, as with -`/users/delta` that returns the changes to the users' collection - or to some logical parent resource, where the change records are implied to be relative to all collections contained within the parent, for example - `/me/planner/all/delta` – this returns changes to any resource within planner that a user is subscribed to as a heterogenous collection. -- API producers should use `$skipToken` and `$deltaToken` within their implementations of `nextLink` and `deltaLink`, however the URLs are defined as being opaque and the existence of the tokens MUST NOT be documented. It is not a breaking change to modify the structure of `nextLinks` or `deltaLinks`.- +- The delta function can be bound to + - an entity collection, as with `/users/delta` that returns the changes to the users' collection, or + - some logical parent resource that returns an entity collection, where the change records are implied to be relative to all collections contained within the parent.For example `/me/planner/all/delta` returns changes to any resource within a planner, which are referenced by 'all' navigation property, and `/communications/onlineMeetings/getAllRecordings/delta` returns changes to any meeting recordings returned by `getAllRecordings` function. + +- API service should use `$skipToken` and `$deltaToken` within their implementations of `nextLink` and `deltaLink`, however the URLs are defined as being opaque and the existence of the tokens MUST NOT be documented. It is not a breaking change to modify the structure of `nextLinks` or `deltaLinks`.- - `nextLink` and `deltaLink` URLs are valid for a specific period before the client application needs to run a full synchronization again.For `nextLink`, a minimal validity time should be 1 hour. For `deltaLink`, a minimal validity time should be seven days. When a link is no longer valid it must return a standard error with a 410 GONE response code. - Although this capability is similar to the OData `$delta` feed 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. - The Graph delta payload format has some deviations from the OData 4.01 change tracking format to simplify parsing, for example the context annotation is removed. +- Additional implementation details are documented [internally](https://dev.azure.com/msazure/One/_wiki/wikis/Microsoft%20Graph%20Partners/211718/Deltas). ## Alternatives @@ -116,6 +117,41 @@ API consumers need guaranteed data integrity over the set of changes to Microsof ``` +### Change tracking on function that return an entity collection + +Firstly, an API designer needs to define the function as composable (so that a delta function can be added to it), by adding the `IsComposable` annotation: + +```xml + + + + +``` + +Next, define the `delta` function. The binding parameter and the return type of the delta function MUST be the same as the return type of the target `getAllRecordings` function: + +```xml + + + + +``` +Finally, for the function, the designer needs to add an annotation (either as a child of the entity or by targeting the entity type as below) stating that it supports change tracking (delta query): + +```xml + + + + + + + +``` +Here is the HTTP request to start the change tracking process on `getAllRecordings` + +```http +GET https://graph.microsoft.com/v1.0/communications/onlineMeetings/getAllRecordings/delta +``` ### Delta payload From 1d284d0cebeb8f6945cde766db1cee83860189a5 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 16 Nov 2022 15:33:41 -0600 Subject: [PATCH 508/729] Clarify allowed/expected client modification of api-version in Operation-Location header --- azure/Guidelines.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 14736bd6..b096bf75 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1043,6 +1043,8 @@ For all long-running operations, the client will issue a GET on a status monitor :ballot_box_with_check: **YOU SHOULD** allow any valid value of the `api-version` query parameter to be used in the get operation on the status monitor. +Note: Clients may replace the value of `api-version` in the `Operation-Location` URI with a value appropriate for their application. + :white_check_mark: **DO** return a status monitor in the response body that conforms with the following structure: From 0595b25465355c181216feacb7791787ae07f647 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 7 Dec 2022 15:45:24 -0500 Subject: [PATCH 509/729] updated ref table --- graph/GuidelinesGraph.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 7c1b6db3..02203adb 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -378,6 +378,7 @@ The guidelines in previous sections are intentionally brief and provide a jump s |--------------------------------------------------|----------------------------------------------------------------------------| | [Alternate key](./patterns/alternate-key.md) | Uniquely identify and query resources using an alternate key. | | [Change tracking](./patterns/change-tracking.md) | Keep API consumers in sync with changes without polling. | +| [Collection subsets](./patterns/subsets.md) | Model collection subsets | | [Dictionary](./patterns/dictionary.md) | Clients can provide an unknown quantity of data elements of the same type. | | [Evolvable enums](./patterns/evolvable-enums.md) | Extend enumerated types without breaking changes. | | [Facets](./patterns/facets.md) | Model parent-child relationships. | @@ -385,6 +386,8 @@ The guidelines in previous sections are intentionally brief and provide a jump s | [Long running operations](./patterns/longRunningOperations.md)| Model operations where processing a client request takes a long time. | | [Modeling subsets](./patterns/subsets.md) | Model collection subsets for All, None, Included, or Excluded criteria. | | [Namespace](./patterns/namespace.md) | Organize resource definitions into a logical set. | +| [Navigation properties](./patterns/navigation-property.md) | Model resource relationships | +| [Operations](./patterns/operations.md) | Model complex business operations | | [Type hierarchy](./patterns/subtypes.md) | Model `is-a` relationships using subtypes. | ## References From 11df3d954d67177639ed5ca464ff447f16db3951 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 21 Dec 2022 07:38:39 -0600 Subject: [PATCH 510/729] Add examples of azure-deprecating header --- azure/Guidelines.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index b096bf75..567d79b2 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -914,18 +914,20 @@ The purpose is to inform customers (when debugging/logging responses) that they Deprecations should use the following pattern: ```text - will retire on (`url`); + will retire on () ``` +Multiple deprecations are allowed, semicolon delimited. + Where the following placeholders should be provided: - `description`: a human-readable description of what is being deprecated - `date`: the target date that this will be deprecated. This should be expressed following the format in [ISO 8601](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "2022-10-31". - `url`: a fully qualified url that the user can follow to learn more about what is being deprecated, preferably to Azure Updates. For example: -```text -azure-deprecating: API version 2009-27-07 will retire on 2022-12-01 (https://azure.microsoft.com/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will retire on 2020-10-30 (https://azure.microsoft.com/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/) -``` +- `azure-deprecating: API version 2009-27-07 will retire on 2022-12-01 (https://azure.microsoft.com/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will retire on 2020-10-30 (https://azure.microsoft.com/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/)` +- `azure-deprecating: Model version 2021-01-15 used in Sentiment analysis will retire on 2022-12-01 (https://aka.ms/ta-modelversions?sentimentAnalysis)` +- `azure-deprecating: TLS 1.0 & 1.1 support will retire on 2022-10-01 (https://devblogs.microsoft.com/devops/deprecating-weak-cryptographic-standards-tls-1-0-and-1-1-in-azure-devops-services/)` :no_entry: **DO NOT** introduce this header without approval from [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) and an official deprecation notice on [Azure Updates](https://azure.microsoft.com/updates/). From fa7b158049d3e3f0d37034b30f1682d0af5963ef Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 21 Dec 2022 08:08:39 -0600 Subject: [PATCH 511/729] Return 200 from POST Actions --- azure/Guidelines.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index b096bf75..27c57318 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -157,7 +157,7 @@ Method | Description | Response Status Code PATCH | Create/Modify the resource with JSON Merge Patch | `200-OK`, `201-Created` PUT | Create/Replace the _whole_ resource | `200-OK`, `201-Created` POST | Create new resource (ID set by service) | `201-Created` with URL of created resource -POST | Action | `200-OK`, `204-No Content` (only when nothing returned in response body) +POST | Action | `200-OK` GET | Read (i.e. list) a resource collection | `200-OK` GET | Read the resource | `200-OK` DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` @@ -174,6 +174,9 @@ DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` :white_check_mark: **DO** return a `204-No Content` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return `404-Not Found`) + +:white_check_mark: **DO** return a `200-OK` from a POST Action. Include a body in the response, even if it has not properties, to allow properties to be added in the future if needed. + :white_check_mark: **DO** return a `403-Forbidden` when the user does not have access to the resource _unless_ this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be `404-Not Found`. [Rationale: a `403-Forbidden` is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.] From eefd5d10312e446162b322746b6c4217282ce438 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 18 Jan 2023 08:20:12 -0600 Subject: [PATCH 512/729] Add example date-time names --- azure/ConsiderationsForServiceDesign.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 1b8842f7..b8602d96 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -118,6 +118,16 @@ For example, `nextUrl` not `nextURL`. :ballot_box_with_check: **YOU SHOULD** use "DateTime" suffix in names of `date-time` values. +For example, `createdDateTime` not `createdAt` or `createdTime`. Common date-time properties and their recommended names are: + +| Property Name | Description | +| --- | --- | +| createdDateTime | The date and time the resource was created. | +| lastModifiedDateTime | The date and time the resource was last modified. | +| deletedDateTime | The date and time the resource was deleted. | +| startDateTime | The starting date and time of an interval. | +| endDateTime | The ending date and time of an interval. | + :ballot_box_with_check: **YOU SHOULD** use a suffix of the unit of measurement for values with a clear unit of measurement (such as bytes, miles, and so on). Use a generally accepted abbreviation for the units (e.g. "Km" rather than "Kilometers") when appropriate. :ballot_box_with_check: **YOU SHOULD** use an int for time durations and include the time units in the name. From cf8c4603399ef00b90b2dcee16d13259d9681ce6 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 12 Feb 2023 20:11:26 -0800 Subject: [PATCH 513/729] Add guidelines for polymorphism --- azure/Guidelines.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index e4020025..4f17817e 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -492,6 +492,12 @@ This indicates to client libraries and customers that values of the enumeration :white_check_mark: **DO** document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow. + +:heavy_check_mark: **YOU MAY** return a value for an extensible enum that is not one of the values defined for the api-version specified in the request. + + +:warning: **YOU SHOULD NOT** accept a value for an extensible enum that is not one of the values defined for the api-version specified in the request. + :no_entry: **DO NOT** remove values from your enumeration list as this breaks customer code. @@ -540,6 +546,18 @@ Below is an example of JSON for a Rectangle and Circle: ``` Both Rectangle and Circle have common fields: `kind`, `fillColor`, `lineColor`, and `subscription`. A Rectangle also has `x`, `y`, `width`, and `length` while a Circle has `x`, `y`, and `radius`. The `subscription` is a nested polymorphic type. A `free` subscription has no additional fields and a `paid` subscription has `expiration` and `invoice` fields. + +:ballot_box_with_check: **YOU SHOULD** define the kind field of a polymorphic type to be an extensible enum. + + +:warning: **YOU SHOULD NOT** allow an update (patch) to change the kind field of a polymorphic type. + + +:warning: **YOU SHOULD NOT** return properties of a polymorphic type that are not defined for the api-version specified in the request. + + +:warning: **YOU SHOULD NOT** specify a polymorphic type as the type of an array property. + ## Common API Patterns From 83cedaa83f64c187604edeb54832a4f76d8c3b06 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Tue, 21 Feb 2023 07:23:08 -0800 Subject: [PATCH 514/729] Revise date-time naming guidance to use At suffix --- azure/ConsiderationsForServiceDesign.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index b8602d96..a7799662 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -116,17 +116,9 @@ For example, `collectedItems` not `itemsCollected` For example, `nextUrl` not `nextURL`. -:ballot_box_with_check: **YOU SHOULD** use "DateTime" suffix in names of `date-time` values. +:ballot_box_with_check: **YOU SHOULD** use an "At" suffix in names of `date-time` values. -For example, `createdDateTime` not `createdAt` or `createdTime`. Common date-time properties and their recommended names are: - -| Property Name | Description | -| --- | --- | -| createdDateTime | The date and time the resource was created. | -| lastModifiedDateTime | The date and time the resource was last modified. | -| deletedDateTime | The date and time the resource was deleted. | -| startDateTime | The starting date and time of an interval. | -| endDateTime | The ending date and time of an interval. | +For example, `createdAt` not `created` or `createdDateTime`. :ballot_box_with_check: **YOU SHOULD** use a suffix of the unit of measurement for values with a clear unit of measurement (such as bytes, miles, and so on). Use a generally accepted abbreviation for the units (e.g. "Km" rather than "Kilometers") when appropriate. @@ -152,8 +144,9 @@ The following are recommended names for properties that match the associated des | Name | Description | |------------- | --- | -| createdDateTime | The date-time that the resource was created | -| updatedDateTime | The date-time that the resource was last updated/modified | +| createdAt | The date and time the resource was created. | +| lastModifiedAt | The date and time the resource was last modified. | +| deletedAt | The date and time the resource was deleted. | | kind | The discriminator value for a polymorphic resource | ### `name` vs `id` From 8d23400e248050f808645a6c46b227d8d949c62d Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Tue, 28 Feb 2023 10:05:48 -0500 Subject: [PATCH 515/729] added link to versioning policy --- azure/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/azure/README.md b/azure/README.md index cec10619..e33064f5 100644 --- a/azure/README.md +++ b/azure/README.md @@ -5,6 +5,7 @@ Designing powerful APIs with strong defaults, consistent behavior across related * [Considerations for Service Design](ConsiderationsForServiceDesign.md) * [REST API Guidelines](Guidelines.md) * [OpenAPI Style Guidelines](https://github.com/Azure/azure-api-style-guide/blob/main/README.md) +* [Versioning policy for Azure services, SDKs, and CLI tools](https://learn.microsoft.com/en-us/azure/developer/intro/azure-service-sdk-tool-versioning) * [Breaking Changes](http://aka.ms/AzBreakingChangesPolicy/) Note: Internal Microsoft link You can reach out to us via [email](mailto://azureapirbcore@microsoft.com) or in our [Teams](https://teams.microsoft.com/l/team/19%3a3ebb18fded0e47938f998e196a52952f%40thread.tacv2/conversations?groupId=1a10b50c-e870-4fe0-8483-bf5542a8d2d8&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) channel. From 8765c85bc5464d083418d50ad5cfeddb0e3b4c6f Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 8 Mar 2023 16:17:26 -0600 Subject: [PATCH 516/729] Update azure/Guidelines.md --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 4f17817e..d84518e2 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -556,7 +556,7 @@ Both Rectangle and Circle have common fields: `kind`, `fillColor`, `lineColor`, :warning: **YOU SHOULD NOT** return properties of a polymorphic type that are not defined for the api-version specified in the request. -:warning: **YOU SHOULD NOT** specify a polymorphic type as the type of an array property. +:warning: **YOU SHOULD NOT** have a property whose value is an array of polymorphic objects. ## Common API Patterns From e02baff5f96dede701a376c1c96f5351aab91930 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Thu, 16 Mar 2023 19:09:01 -0400 Subject: [PATCH 517/729] Update README.md Fixed breaking changes link --- azure/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/README.md b/azure/README.md index e33064f5..b4beb182 100644 --- a/azure/README.md +++ b/azure/README.md @@ -6,7 +6,7 @@ Designing powerful APIs with strong defaults, consistent behavior across related * [REST API Guidelines](Guidelines.md) * [OpenAPI Style Guidelines](https://github.com/Azure/azure-api-style-guide/blob/main/README.md) * [Versioning policy for Azure services, SDKs, and CLI tools](https://learn.microsoft.com/en-us/azure/developer/intro/azure-service-sdk-tool-versioning) -* [Breaking Changes](http://aka.ms/AzBreakingChangesPolicy/) Note: Internal Microsoft link +* [Breaking Changes](http://aka.ms/AzBreakingChangePolicy/) Note: Internal Microsoft link You can reach out to us via [email](mailto://azureapirbcore@microsoft.com) or in our [Teams](https://teams.microsoft.com/l/team/19%3a3ebb18fded0e47938f998e196a52952f%40thread.tacv2/conversations?groupId=1a10b50c-e870-4fe0-8483-bf5542a8d2d8&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) channel. From 6bf55a7fca923e6ec3ddec0d34f919bd334ccd2f Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 20 Mar 2023 17:26:28 -0500 Subject: [PATCH 518/729] Improve/explain polymorphic types in arrays guidance --- azure/Guidelines.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index d84518e2..8e933463 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -556,7 +556,9 @@ Both Rectangle and Circle have common fields: `kind`, `fillColor`, `lineColor`, :warning: **YOU SHOULD NOT** return properties of a polymorphic type that are not defined for the api-version specified in the request. -:warning: **YOU SHOULD NOT** have a property whose value is an array of polymorphic objects. +:warning: **YOU SHOULD NOT** have a property of an updatable resource whose value is an array of polymorphic objects. + +Updating an array property with JSON merge-patch is not version-resilient if the array contains polymorphic types. ## Common API Patterns From cfc2fdadd4ac011c274f72303db993d44d0581d1 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 20 Mar 2023 18:07:45 -0500 Subject: [PATCH 519/729] Discriminator name should be 'kind' --- azure/Guidelines.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 8e933463..e45938b7 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -509,9 +509,9 @@ This indicates to client libraries and customers that values of the enumeration If you can't avoid them, then follow the guideline below. -:white_check_mark: **DO** define a `kind` field indicating the kind of the resource and include any kind-specific fields in the body. +:white_check_mark: **DO** define a discriminator field indicating the kind of the resource and include any kind-specific fields in the body. -Below is an example of JSON for a Rectangle and Circle: +Below is an example of JSON for a Rectangle and Circle with a discriminator field named `kind`: **Rectangle** ```json { @@ -546,11 +546,13 @@ Below is an example of JSON for a Rectangle and Circle: ``` Both Rectangle and Circle have common fields: `kind`, `fillColor`, `lineColor`, and `subscription`. A Rectangle also has `x`, `y`, `width`, and `length` while a Circle has `x`, `y`, and `radius`. The `subscription` is a nested polymorphic type. A `free` subscription has no additional fields and a `paid` subscription has `expiration` and `invoice` fields. +The [Azure Naming Guidelines](./ConsiderationsForServiceDesign.md#common-names) recommend that the discriminator field be named `kind`. + -:ballot_box_with_check: **YOU SHOULD** define the kind field of a polymorphic type to be an extensible enum. +:ballot_box_with_check: **YOU SHOULD** define the discriminator field of a polymorphic type to be an extensible enum. -:warning: **YOU SHOULD NOT** allow an update (patch) to change the kind field of a polymorphic type. +:warning: **YOU SHOULD NOT** allow an update (patch) to change the discriminator field of a polymorphic type. :warning: **YOU SHOULD NOT** return properties of a polymorphic type that are not defined for the api-version specified in the request. From 67839135cc3c325869c94cc43a5ee8e380fd9811 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 20 Mar 2023 19:09:56 -0500 Subject: [PATCH 520/729] Alternatives for update of discriminator value --- azure/Guidelines.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index e45938b7..d5c380d5 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -551,9 +551,16 @@ The [Azure Naming Guidelines](./ConsiderationsForServiceDesign.md#common-names) :ballot_box_with_check: **YOU SHOULD** define the discriminator field of a polymorphic type to be an extensible enum. +**WE SHOULD CHOOSE ONE OF THE FOLLOWING TWO OPTIONS** + :warning: **YOU SHOULD NOT** allow an update (patch) to change the discriminator field of a polymorphic type. +**OR** + + +:ballot_box_with_check: **YOU SHOULD** remove all properties specific to the old discriminator value from the resource when an update (patch) changes the discriminator field. + :warning: **YOU SHOULD NOT** return properties of a polymorphic type that are not defined for the api-version specified in the request. From 28bc3ef5674244dbe22fd9ce6baf204dd3656352 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Fri, 24 Mar 2023 10:40:57 -0400 Subject: [PATCH 521/729] Clarifed the positioning of the MS Guidelines. --- Guidelines.md | 19 ++++++++++--------- README.md | 20 +++++++++++++------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Guidelines.md b/Guidelines.md index 766f679f..524112c9 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -1,3 +1,13 @@ +> # NOTICE TO READERS +> +> ## **Guidance for Azure service teams** +> Azure service teams should use the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. +> +> ## **Guidance for Microsoft Graph service teams** +> Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. + +--- + # Microsoft REST API Guidelines ## Microsoft REST API Guidelines Working Group @@ -20,13 +30,6 @@ To provide the smoothest possible experience for developers on platforms followi This document establishes the guidelines Microsoft REST APIs SHOULD follow so RESTful interfaces are developed consistently. -### **Guidance for Azure service teams** -Azure service teams should use the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. - -### **Guidance for Microsoft Graph service teams** -Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. - - ## 2. Table of contents @@ -34,8 +37,6 @@ Graph service teams should reference the companion document, [Graph REST API Gui - [Microsoft REST API Guidelines Working Group](#microsoft-rest-api-guidelines-working-group) - [Microsoft REST API Guidelines](#microsoft-rest-api-guidelines-1) - [1. Abstract](#1-abstract) - - [**Guidance for Azure service teams**](#guidance-for-azure-service-teams) - - [**Guidance for Microsoft Graph service teams**](#guidance-for-microsoft-graph-service-teams) - [2. Table of contents](#2-table-of-contents) - [3. Introduction](#3-introduction) - [3.1. Recommended reading](#31-recommended-reading) diff --git a/README.md b/README.md index 8ac0bbf4..8d6e5058 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ -# Microsoft REST API Guidelines +> # NOTICE TO READERS +> +> ## Guidance for Azure service teams +> Azure service teams should use companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. +> +> ## Guidance for Microsoft Graph service teams +> Microsoft Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. +> +> In the process of building many of Microsoft's highest scale services, the Microsoft Graph team found the Microsoft API guidelines tremendously useful as a baseline. However, there are several areas where we need to provide more clarity on how developers should describe their APIs. The companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) is a set of amendments and clarifications for Microsoft Graph that act as further reading. Recognizing that two documents is a lot for a new API designer to absorb, our plan is to follow the approach Azure have taken and roll out guidelines for Microsoft Graph into a single consolidated document. + +--- + +## Microsoft REST API Guidelines The [Microsoft REST API Guidelines](Guidelines.md) are Microsoft's internal company-wide REST API design guidelines. Teams at Microsoft typically reference this document when setting API design policy. They may additionally create documents specific to their team, adding further guidance or making adjustments as appropriate to their circumstances. @@ -6,12 +18,6 @@ They may additionally create documents specific to their team, adding further gu We publish these guidelines here with the aim of fostering dialogue and learning in the API community at large. We further hope that these guidelines may encourage other organizations to create guidelines that are appropriate for them and in turn, if they are able, to publish theirs. -### Guidance for Azure service teams -Azure service teams should use companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. - -### Guidance for Microsoft Graph service teams -Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. - [![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) ## Code of Conduct From 2f96c45cc1d3f5a9d0be3ff18b15311410943f07 Mon Sep 17 00:00:00 2001 From: Mark Weitzel Date: Wed, 29 Mar 2023 08:17:27 -0400 Subject: [PATCH 522/729] Fixed broken link to breaking changes --- azure/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/README.md b/azure/README.md index b4beb182..1daae552 100644 --- a/azure/README.md +++ b/azure/README.md @@ -6,7 +6,7 @@ Designing powerful APIs with strong defaults, consistent behavior across related * [REST API Guidelines](Guidelines.md) * [OpenAPI Style Guidelines](https://github.com/Azure/azure-api-style-guide/blob/main/README.md) * [Versioning policy for Azure services, SDKs, and CLI tools](https://learn.microsoft.com/en-us/azure/developer/intro/azure-service-sdk-tool-versioning) -* [Breaking Changes](http://aka.ms/AzBreakingChangePolicy/) Note: Internal Microsoft link +* [Breaking Changes](https://aka.ms/azapi/breakingchanges) Note: Internal Microsoft link You can reach out to us via [email](mailto://azureapirbcore@microsoft.com) or in our [Teams](https://teams.microsoft.com/l/team/19%3a3ebb18fded0e47938f998e196a52952f%40thread.tacv2/conversations?groupId=1a10b50c-e870-4fe0-8483-bf5542a8d2d8&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) channel. From 8c006935b022e9308eb0b418eb5102fa7873cf71 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 5 Apr 2023 06:11:45 -0500 Subject: [PATCH 523/729] Kind in polymorphic type should be immutable --- azure/Guidelines.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index d5c380d5..e45938b7 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -551,16 +551,9 @@ The [Azure Naming Guidelines](./ConsiderationsForServiceDesign.md#common-names) :ballot_box_with_check: **YOU SHOULD** define the discriminator field of a polymorphic type to be an extensible enum. -**WE SHOULD CHOOSE ONE OF THE FOLLOWING TWO OPTIONS** - :warning: **YOU SHOULD NOT** allow an update (patch) to change the discriminator field of a polymorphic type. -**OR** - - -:ballot_box_with_check: **YOU SHOULD** remove all properties specific to the old discriminator value from the resource when an update (patch) changes the discriminator field. - :warning: **YOU SHOULD NOT** return properties of a polymorphic type that are not defined for the api-version specified in the request. From 4559735677941bcc2300c276813ec30d93509762 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 7 Apr 2023 13:55:59 -0500 Subject: [PATCH 524/729] Add change history entry and enclose in collapsible section --- azure/Guidelines.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index e45938b7..23410e47 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -11,9 +11,13 @@ Please ensure that you add an anchor tag to any new guidelines that you add and ## History +
    + Expand change history + | Date | Notes | | ----------- | -------------------------------------------------------------- | -| 2022-Sep-07 | Updated URL guidelines for DNS Done Right | | +| 2023-Apr-07 | Update/clarify guidelines on polymorphism | +| 2022-Sep-07 | Updated URL guidelines for DNS Done Right | | 2022-Jul-15 | Update guidance on long-running operations | | 2022-May-11 | Drop guidance on version discovery | | 2022-Mar-29 | Add guidelines about using durations | @@ -24,6 +28,8 @@ Please ensure that you add an anchor tag to any new guidelines that you add and | 2020-Jul-31 | Added service advice for initial versions | | 2020-Mar-31 | 1st public release of the Azure REST API Guidelines | +
    + ## Introduction These guidelines apply to Azure service teams implementing _data plane_ APIs. They offer prescriptive guidance that Azure service teams MUST follow ensuring that customers have a great experience by designing APIs meeting these goals: From d50c5b29d2d79cd8ef96b3fbceaf2e6b3e942b5a Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Wed, 12 Apr 2023 15:06:10 -0700 Subject: [PATCH 525/729] Fix anchors in guidelines --- azure/Guidelines.md | 638 +++++++++++++++----------------------------- 1 file changed, 217 insertions(+), 421 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 75f6a71e..a7fd7b9f 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -62,7 +62,7 @@ This document offers prescriptive guidance labeled as follows: ## Building Blocks: HTTP, REST, & JSON The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet; namely HTTP, REST, and JSON. This section provides you with a general understanding of how these technologies should be applied when creating your service. - + ### HTTP Azure services must adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231). This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: @@ -74,8 +74,7 @@ Azure services must adhere to the HTTP specification, [RFC7231](https://tools.ie A Uniform Resource Locator (URL) is how developers access the resources of your service. Ultimately, URLs are how developers form a cognitive model of your service's resources. - -:white_check_mark: **DO** use this URL pattern: +:white_check_mark: **DO** use this URL pattern: ```text https://.../// ``` @@ -91,34 +90,25 @@ Where: | resource‑collection | Name of the collection, unabbreviated, pluralized | resource‑id | Id of resource within the resource-collection. This MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. - -:white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. +:white_check_mark: **DO** use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing. - -:white_check_mark: **DO** return `414-URI Too Long` if a URL exceeds 2083 characters +:white_check_mark: **DO** return `414-URI Too Long` if a URL exceeds 2083 characters - -:white_check_mark: **DO** treat service-defined URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request **MUST** fail with a `404-Not found` HTTP return code. +:white_check_mark: **DO** treat service-defined URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request **MUST** fail with a `404-Not found` HTTP return code. Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2' - -:white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a JSON response body +:white_check_mark: **DO** ensure proper casing when returning a URL in an HTTP response header value or inside a JSON response body - -:white_check_mark: **DO** restrict the characters in service-defined path segments to `0-9 A-Z a-z - . _ ~`, with `:` allowed only as described below to designate an action operation. +:white_check_mark: **DO** restrict the characters in service-defined path segments to `0-9 A-Z a-z - . _ ~`, with `:` allowed only as described below to designate an action operation. - -:ballot_box_with_check: **YOU SHOULD** restrict the characters allowed in user-specified path segments (i.e. path parameters values) to `0-9 A-Z a-z - . _ ~` (do not allow `:`). +:ballot_box_with_check: **YOU SHOULD** restrict the characters allowed in user-specified path segments (i.e. path parameters values) to `0-9 A-Z a-z - . _ ~` (do not allow `:`). - -:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) +:ballot_box_with_check: **YOU SHOULD** keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz) - -:heavy_check_mark: **YOU MAY** use these other characters in the URL path but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` +:heavy_check_mark: **YOU MAY** use these other characters in the URL path but they will likely require %-encoding [[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1)]: `/ ? # [ ] @ ! $ & ' ( ) * + , ; =` - -:heavy_check_mark: **YOU MAY** support a direct endpoint URL for performance/routing: +:heavy_check_mark: **YOU MAY** support a direct endpoint URL for performance/routing: ```text https://-../... ``` @@ -128,11 +118,9 @@ Examples: - Response header ([RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4)): `content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2` - GUID format: `https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2` - -:white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for `` or always a single verified domain. +:white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for `` or always a single verified domain. - -:heavy_check_mark: **YOU MAY** use URLs as values +:heavy_check_mark: **YOU MAY** use URLs as values ```text https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy ``` @@ -144,19 +132,15 @@ Cloud applications embrace failure. Therefore, to enable customers to write faul ##### Exactly Once Behavior = Client Retries & Service Idempotency - -:white_check_mark: **DO** ensure that _all_ HTTP methods are idempotent. +:white_check_mark: **DO** ensure that _all_ HTTP methods are idempotent. - -:ballot_box_with_check: **YOU SHOULD** use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent. +:ballot_box_with_check: **YOU SHOULD** use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent. - -:heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response **MUST** return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#repeatability-of-requests)). +:heavy_check_mark: **YOU MAY** use POST to create a resource but you must make it idempotent and, of course, the response **MUST** return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See [Repeatability of requests](#repeatability-of-requests)). ##### HTTP Return Codes - -:white_check_mark: **DO** adhere to the return codes in the following table when the method completes synchronously and is successful: +:white_check_mark: **DO** adhere to the return codes in the following table when the method completes synchronously and is successful: Method | Description | Response Status Code -------|-------------|--------------------- @@ -168,35 +152,26 @@ GET | Read (i.e. list) a resource collection | `200-OK` GET | Read the resource | `200-OK` DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` - -:white_check_mark: **DO** return status code `202-Accepted` and follow the guidance in [Long-Running Operations & Jobs](#long-running-operations--jobs) when a PUT, POST, or DELETE method completes asynchronously. +:white_check_mark: **DO** return status code `202-Accepted` and follow the guidance in [Long-Running Operations & Jobs](#long-running-operations--jobs) when a PUT, POST, or DELETE method completes asynchronously. - -:white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase +:white_check_mark: **DO** treat method names as case sensitive and should always be in uppercase - -:white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a `200-OK` or `201-Created`. +:white_check_mark: **DO** return the state of the resource after a PUT, PATCH, POST, or GET operation with a `200-OK` or `201-Created`. - -:white_check_mark: **DO** return a `204-No Content` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return `404-Not Found`) +:white_check_mark: **DO** return a `204-No Content` without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return `404-Not Found`) - -:white_check_mark: **DO** return a `200-OK` from a POST Action. Include a body in the response, even if it has not properties, to allow properties to be added in the future if needed. +:white_check_mark: **DO** return a `200-OK` from a POST Action. Include a body in the response, even if it has not properties, to allow properties to be added in the future if needed. - -:white_check_mark: **DO** return a `403-Forbidden` when the user does not have access to the resource _unless_ this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be `404-Not Found`. [Rationale: a `403-Forbidden` is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.] +:white_check_mark: **DO** return a `403-Forbidden` when the user does not have access to the resource _unless_ this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be `404-Not Found`. [Rationale: a `403-Forbidden` is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.] - -:white_check_mark: **DO** support caching and optimistic concurrency by honoring the the `If-Match`, `If-None-Match`, if-modified-since, and if-unmodified-since request headers and by returning the ETag and last-modified response headers +:white_check_mark: **DO** support caching and optimistic concurrency by honoring the the `If-Match`, `If-None-Match`, if-modified-since, and if-unmodified-since request headers and by returning the ETag and last-modified response headers #### HTTP Query Parameters and Header Values Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. - -:white_check_mark: **DO** validate all query parameter and request header values and fail the operation with `400-Bad Request` if any value fails validation. Return an error response as described in the [Handling Errors](#handling-errors) section indicating what is wrong so customer can diagnose the issue and fix it themselves. +:white_check_mark: **DO** validate all query parameter and request header values and fail the operation with `400-Bad Request` if any value fails validation. Return an error response as described in the [Handling Errors](#handling-errors) section indicating what is wrong so customer can diagnose the issue and fix it themselves. - -:white_check_mark: **DO** use the following table when translating strings: +:white_check_mark: **DO** use the following table when translating strings: Data type | Document that string must be --------- | ------- @@ -234,40 +209,32 @@ _x-ms-error-code_ | Response | (see [Handling Errors](#handling-errors)) _azure-deprecating_ | Response | (see [Deprecating Behavior Notification](#deprecating-behavior-notification)) retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)) - -:white_check_mark: **DO** support all headers shown in _italics_ +:white_check_mark: **DO** support all headers shown in _italics_ - -:white_check_mark: **DO** specify headers using kebab-casing +:white_check_mark: **DO** specify headers using kebab-casing - -:white_check_mark: **DO** compare request header names using case-insensitivity +:white_check_mark: **DO** compare request header names using case-insensitivity - -:white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it +:white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it - -:white_check_mark: **DO** accept date values in headers in HTTP-Date format and return date values in headers in the IMF-fixdate format as defined in [RFC7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT". +:white_check_mark: **DO** accept date values in headers in HTTP-Date format and return date values in headers in the IMF-fixdate format as defined in [RFC7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT". Note: The RFC 7321 IMF-fixdate format is a "fixed-length and single-zone subset" of the RFC 1123 / RFC 5822 format, which means: a) year must be four digits, b) the seconds component of time is required, and c) the timezone must be GMT. - -:white_check_mark: **DO** create an opaque value that uniquely identifies the request and return this value in the `x-ms-request-id` response header. +:white_check_mark: **DO** create an opaque value that uniquely identifies the request and return this value in the `x-ms-request-id` response header. Your service should include the `x-ms-request-id` value in error logs so that users can submit support requests for specific failures using this value. - -:no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated +:no_entry: **DO NOT** fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated - -:no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. +:no_entry: **DO NOT** use "x-" prefix for custom headers, unless the header already exists in production [[RFC 6648](https://datatracker.ietf.org/doc/html/rfc6648)]. **Additional References** - [StackOverflow - Difference between http parameters and http headers](https://stackoverflow.com/questions/40492782) - [Standard HTTP Headers](https://httpwg.org/specs/rfc7231.html#header.field.registration) - [Why isn't HTTP PUT allowed to do partial updates in a REST API?](https://stackoverflow.com/questions/19732423/why-isnt-http-put-allowed-to-do-partial-updates-in-a-rest-api) - + ### REpresentational State Transfer (REST) REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's [URLs](#uniform-resource-locators-urls) determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. @@ -275,28 +242,21 @@ There are patterns, later in these guidelines, that describe how to invoke behav When designing your service, it is important to optimize for the developer using your API. - -:white_check_mark: **DO** focus heavily on clear & consistent naming +:white_check_mark: **DO** focus heavily on clear & consistent naming - -:white_check_mark: **DO** ensure your resource paths make sense +:white_check_mark: **DO** ensure your resource paths make sense - -:white_check_mark: **DO** simplify operations with few required query parameters & JSON fields +:white_check_mark: **DO** simplify operations with few required query parameters & JSON fields - -:white_check_mark: **DO** establish clear contracts for string values +:white_check_mark: **DO** establish clear contracts for string values - -:white_check_mark: **DO** use proper response codes/bodies so customer can diagnose their own problems and fix them without contacting Azure support or the service team +:white_check_mark: **DO** use proper response codes/bodies so customer can diagnose their own problems and fix them without contacting Azure support or the service team #### Resource Schema & Field Mutability - -:white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH response, GET response, and POST request/response on a given URL path. The PATCH request schema should contain all the same fields with no required fields. This allows one SDK type for input/output operations and enables the response to be passed back in a request. +:white_check_mark: **DO** use the same JSON schema for PUT request/response, PATCH response, GET response, and POST request/response on a given URL path. The PATCH request schema should contain all the same fields with no required fields. This allows one SDK type for input/output operations and enables the response to be passed back in a request. - -:white_check_mark: **DO** think about your resource's fields and how they are used: +:white_check_mark: **DO** think about your resource's fields and how they are used: Field Mutability | Service Request's behavior for this field -----------------| ----------------------------------------- @@ -307,37 +267,27 @@ Field Mutability | Service Request's behavior for this field In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically _not_ become a nullable field in a SDK's data structure. This allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa. - -:white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. +:white_check_mark: **DO** make fields simple and maintain a shallow hierarchy. - -:white_check_mark: **DO** use GET for resource retrieval and return JSON in the response body +:white_check_mark: **DO** use GET for resource retrieval and return JSON in the response body - -:white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. +:white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. - -:white_check_mark: **DO** use PUT with JSON for wholesale create/replace operations. **NOTE:** If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT). +:white_check_mark: **DO** use PUT with JSON for wholesale create/replace operations. **NOTE:** If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT). - -:white_check_mark: **DO** use DELETE to remove a resource. +:white_check_mark: **DO** use DELETE to remove a resource. - -:white_check_mark: **DO** fail an operation with `400-Bad Request` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in [Handling errors](#handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. +:white_check_mark: **DO** fail an operation with `400-Bad Request` if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in [Handling errors](#handling-errors) indicating what is wrong so customer can diagnose the issue and fix it themselves. - -:heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. +:heavy_check_mark: **YOU MAY** return secret fields via POST **if absolutely necessary**. - -:no_entry: **DO NOT** return secret fields via GET. For example, do not return `administratorPassword` in JSON. +:no_entry: **DO NOT** return secret fields via GET. For example, do not return `administratorPassword` in JSON. - -:no_entry: **DO NOT** add fields to the JSON if the value is easily computable from other fields to avoid bloating the body. +:no_entry: **DO NOT** add fields to the JSON if the value is easily computable from other fields to avoid bloating the body. ##### Create / Update / Replace Processing Rules - -:white_check_mark: **DO** follow the processing below to create/update/replace a resource: +:white_check_mark: **DO** follow the processing below to create/update/replace a resource: When using this method | if this condition happens | use this response code ---------------------- | ------------------------- | ---------------------- @@ -357,28 +307,21 @@ There are 2 kinds of errors: - An error where you expect customer code to gracefully recover at runtime - An error indicating a bug in customer code that is unlikely to be recoverable at runtime; the customer must just fix their code - -:white_check_mark: **DO** return an `x-ms-error-code` response header with a string error code indicating what went wrong. +:white_check_mark: **DO** return an `x-ms-error-code` response header with a string error code indicating what went wrong. *NOTE: `x-ms-error-code` values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future.* - -:heavy_check_mark: **YOU MAY** implement the `x-ms-error-code` values as an enum with `"modelAsString": true` because it's possible add new values over time. In particular, it's only a breaking change if the same conditions result in a *different* top-level error code. +:heavy_check_mark: **YOU MAY** implement the `x-ms-error-code` values as an enum with `"modelAsString": true` because it's possible add new values over time. In particular, it's only a breaking change if the same conditions result in a *different* top-level error code. - -:warning: **YOU SHOULD NOT** add new top-level error codes to an existing API without bumping the service version. +:warning: **YOU SHOULD NOT** add new top-level error codes to an existing API without bumping the service version. - -:white_check_mark: **DO** carefully craft unique `x-ms-error-code` string values for errors that are recoverable at runtime. Reuse common error codes for usage errors that are not recoverable. +:white_check_mark: **DO** carefully craft unique `x-ms-error-code` string values for errors that are recoverable at runtime. Reuse common error codes for usage errors that are not recoverable. - -:heavy_check_mark: **YOU MAY** group common customer code errors into a few `x-ms-error-code` string values. +:heavy_check_mark: **YOU MAY** group common customer code errors into a few `x-ms-error-code` string values. - -:white_check_mark: **DO** ensure that the top-level error's `code` value is identical to the `x-ms-error-code` header's value. +:white_check_mark: **DO** ensure that the top-level error's `code` value is identical to the `x-ms-error-code` header's value. - -:white_check_mark: **DO** provide a response body with the following structure: +:white_check_mark: **DO** provide a response body with the following structure: **ErrorResponse** : Object @@ -419,31 +362,24 @@ Example: } ``` - -:white_check_mark: **DO** document the service's top-level error code strings; they are part of the API contract. +:white_check_mark: **DO** document the service's top-level error code strings; they are part of the API contract. - -:heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are _not_ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. +:heavy_check_mark: **YOU MAY** treat the other fields as you wish as they are _not_ considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues. - -:heavy_check_mark: **YOU MAY** add additional properties for any data values in your error message so customers don't resort to parsing your error message. For example, an error with `"message": "A maximum of 16 keys are allowed per account."` might also add a `"maximumKeys": 16` property. This is not part of your API contract and should only be used for diagnosing problems. +:heavy_check_mark: **YOU MAY** add additional properties for any data values in your error message so customers don't resort to parsing your error message. For example, an error with `"message": "A maximum of 16 keys are allowed per account."` might also add a `"maximumKeys": 16` property. This is not part of your API contract and should only be used for diagnosing problems. *Note: Do not use this mechanism to provide information developers need to rely on in code (ex: the error message can give details about why you've been throttled, but the `Retry-After` should be what developers rely on to back off).* - -:warning: **YOU SHOULD NOT** document specific error status codes in your OpenAPI/Swagger spec unless the "default" response cannot properly describe the specific error response (e.g. body schema is different). +:warning: **YOU SHOULD NOT** document specific error status codes in your OpenAPI/Swagger spec unless the "default" response cannot properly describe the specific error response (e.g. body schema is different). - + ### JSON - -:white_check_mark: **DO** use camel case for all JSON field names. Do not upper-case acronyms; use camel case. +:white_check_mark: **DO** use camel case for all JSON field names. Do not upper-case acronyms; use camel case. - -:white_check_mark: **DO** treat JSON field names with case-sensitivity. +:white_check_mark: **DO** treat JSON field names with case-sensitivity. - -:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions (e.g. GUIDs) but avoid if at all possible. +:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions (e.g. GUIDs) but avoid if at all possible. Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: @@ -453,38 +389,27 @@ Services, and the clients that access them, may be written in multiple languages Number | Signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) String | Used for everything else - -:white_check_mark: **DO** use integers within the acceptable range of JSON number. +:white_check_mark: **DO** use integers within the acceptable range of JSON number. - -:white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. +:white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. - -:white_check_mark: **DO** use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC3339 for date/time. +:white_check_mark: **DO** use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC3339 for date/time. - -:white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable" across multiple programming languages. +:white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable" across multiple programming languages. - -:white_check_mark: **DO** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) for date/time. +:white_check_mark: **DO** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) for date/time. - -:white_check_mark: **DO** use a fixed time interval to express durations e.g., milliseconds, seconds, minutes, days, etc., and include the time unit in the property name e.g., `backupTimeInMinutes` or `ttlSeconds`. +:white_check_mark: **DO** use a fixed time interval to express durations e.g., milliseconds, seconds, minutes, days, etc., and include the time unit in the property name e.g., `backupTimeInMinutes` or `ttlSeconds`. - -:heavy_check_mark: **YOU MAY** use [RFC3339 time intervals](https://wikipedia.org/wiki/ISO_8601#Durations) only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P3M" represents 3 months no matter how many days between the start and end dates, or "P1Y" represents 366 days on a leap year. The value must be round-trippable. +:heavy_check_mark: **YOU MAY** use [RFC3339 time intervals](https://wikipedia.org/wiki/ISO_8601#Durations) only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P3M" represents 3 months no matter how many days between the start and end dates, or "P1Y" represents 366 days on a leap year. The value must be round-trippable. - -:white_check_mark: **DO** use [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) for UUIDs. +:white_check_mark: **DO** use [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) for UUIDs. - -:heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. +:heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. - -:heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. Avoid arrays in other situations since arrays can be difficult and inefficient to work with, especially with JSON Merge Patch where the entire array needs to be read prior to any operation being applied to it. +:heavy_check_mark: **YOU MAY** use JSON arrays if maintaining an order of values is required. Avoid arrays in other situations since arrays can be difficult and inefficient to work with, especially with JSON Merge Patch where the entire array needs to be read prior to any operation being applied to it. - -:ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays whenever possible. +:ballot_box_with_check: **YOU SHOULD** use JSON objects instead of arrays whenever possible. #### Enums & SDKs (Client libraries) It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI definition as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation. @@ -492,30 +417,23 @@ It is common for strings to have an explicit set of values. These are often refl However, it is not uncommon for the set of values to grow over the life of a service. For this reason, Microsoft's tooling uses the concept of an "extensible enum," which indicates that the set of values should be treated as only a _partial_ list. This indicates to client libraries and customers that values of the enumeration field should be effectively treated as strings and that undocumented value may returned in the future. This enables the set of values to grow over time while ensuring stability in client libraries and customer code. - -:ballot_box_with_check: **YOU SHOULD** use extensible enumerations unless you are positive that the symbol set will NEVER change over time. +:ballot_box_with_check: **YOU SHOULD** use extensible enumerations unless you are positive that the symbol set will NEVER change over time. - -:white_check_mark: **DO** document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow. +:white_check_mark: **DO** document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow. - -:heavy_check_mark: **YOU MAY** return a value for an extensible enum that is not one of the values defined for the api-version specified in the request. +:heavy_check_mark: **YOU MAY** return a value for an extensible enum that is not one of the values defined for the api-version specified in the request. - -:warning: **YOU SHOULD NOT** accept a value for an extensible enum that is not one of the values defined for the api-version specified in the request. +:warning: **YOU SHOULD NOT** accept a value for an extensible enum that is not one of the values defined for the api-version specified in the request. - -:no_entry: **DO NOT** remove values from your enumeration list as this breaks customer code. +:no_entry: **DO NOT** remove values from your enumeration list as this breaks customer code. #### Polymorphic types - -:warning: **YOU SHOULD NOT** use polymorphic JSON types because they greatly complicate the customer code due to runtime dynamic casts and the introduction of new types in the future. +:warning: **YOU SHOULD NOT** use polymorphic JSON types because they greatly complicate the customer code due to runtime dynamic casts and the introduction of new types in the future. If you can't avoid them, then follow the guideline below. - -:white_check_mark: **DO** define a discriminator field indicating the kind of the resource and include any kind-specific fields in the body. +:white_check_mark: **DO** define a discriminator field indicating the kind of the resource and include any kind-specific fields in the body. Below is an example of JSON for a Rectangle and Circle with a discriminator field named `kind`: **Rectangle** @@ -554,28 +472,23 @@ Both Rectangle and Circle have common fields: `kind`, `fillColor`, `lineColor`, The [Azure Naming Guidelines](./ConsiderationsForServiceDesign.md#common-names) recommend that the discriminator field be named `kind`. - -:ballot_box_with_check: **YOU SHOULD** define the discriminator field of a polymorphic type to be an extensible enum. +:ballot_box_with_check: **YOU SHOULD** define the discriminator field of a polymorphic type to be an extensible enum. - -:warning: **YOU SHOULD NOT** allow an update (patch) to change the discriminator field of a polymorphic type. +:warning: **YOU SHOULD NOT** allow an update (patch) to change the discriminator field of a polymorphic type. - -:warning: **YOU SHOULD NOT** return properties of a polymorphic type that are not defined for the api-version specified in the request. +:warning: **YOU SHOULD NOT** return properties of a polymorphic type that are not defined for the api-version specified in the request. - -:warning: **YOU SHOULD NOT** have a property of an updatable resource whose value is an array of polymorphic objects. +:warning: **YOU SHOULD NOT** have a property of an updatable resource whose value is an array of polymorphic objects. Updating an array property with JSON merge-patch is not version-resilient if the array contains polymorphic types. ## Common API Patterns - + ### Performing an Action The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image or rebooting a VM. It is also sometimes useful to perform an action on a collection. - -:ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a resource +:ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a resource **URL Pattern** ```text https://...//:? @@ -586,8 +499,7 @@ https://...//:? https://.../users/Bob:grant?access=read ``` - -:ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a collection +:ballot_box_with_check: **YOU SHOULD** pattern your URL like this to perform an action on a collection **URL Pattern** ```text https://.../:? @@ -600,33 +512,25 @@ https://.../users:grant?access=read Note: To avoid potential collision of actions and resource ids, you should disallow the use of the ":" character in resource ids. - -:white_check_mark: **DO** use a POST operation for any action on a resource or collection. +:white_check_mark: **DO** use a POST operation for any action on a resource or collection. - -:white_check_mark: **DO** support the Repeatability-Request-ID & Repeatability-First-Sent request headers if the action needs to be idempotent if retries occur. +:white_check_mark: **DO** support the Repeatability-Request-ID & Repeatability-First-Sent request headers if the action needs to be idempotent if retries occur. - -:white_check_mark: **DO** return a `200-OK` when the action completes synchronously and successfully. +:white_check_mark: **DO** return a `200-OK` when the action completes synchronously and successfully. - -:ballot_box_with_check: **YOU SHOULD** use a verb as the `` component of the path. +:ballot_box_with_check: **YOU SHOULD** use a verb as the `` component of the path. - -:no_entry: **DO NOT** use an action operation when the operation behavior could reasonably be defined as one of the standard REST Create, Read, Update, Delete, or List operations. +:no_entry: **DO NOT** use an action operation when the operation behavior could reasonably be defined as one of the standard REST Create, Read, Update, Delete, or List operations. - + ### Collections - -:white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources. +:white_check_mark: **DO** structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources. - -:ballot_box_with_check: **YOU SHOULD** support paging today if there is ever a chance in the future that the number of items can grow to be very large. +:ballot_box_with_check: **YOU SHOULD** support paging today if there is ever a chance in the future that the number of items can grow to be very large. NOTE: It is a breaking change to add paging in the future - -:heavy_check_mark: **YOU MAY** expose an operation that lists your resources by supporting a GET method with a URL to a resource-collection (as opposed to a resource-id). +:heavy_check_mark: **YOU MAY** expose an operation that lists your resources by supporting a GET method with a URL to a resource-collection (as opposed to a resource-id). **Example Response Body** ```json @@ -641,36 +545,27 @@ NOTE: It is a breaking change to add paging in the future } ``` - -:white_check_mark: **DO** include the _id_ field and _etag_ field (if supported) for each item as this allows the customer to modify the item in a future operation. +:white_check_mark: **DO** include the _id_ field and _etag_ field (if supported) for each item as this allows the customer to modify the item in a future operation. - -:white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). +:white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). - -:white_check_mark: **DO** return a `nextLink` field with an absolute URL that the client can GET in order to retrieve the next page of the collection. +:white_check_mark: **DO** return a `nextLink` field with an absolute URL that the client can GET in order to retrieve the next page of the collection. Note: The service is responsible for performing any URL-encoding required on the `nextLink` URL. - -:white_check_mark: **DO** include any query parameters required by the service in `nextLink`, including `api-version`. +:white_check_mark: **DO** include any query parameters required by the service in `nextLink`, including `api-version`. - -:ballot_box_with_check: **YOU SHOULD** use `value` as the name of the top-level array field unless a more appropriate name is available. +:ballot_box_with_check: **YOU SHOULD** use `value` as the name of the top-level array field unless a more appropriate name is available. - -:no_entry: **DO NOT** return the `nextLink` field at all when returning the last page of the collection. +:no_entry: **DO NOT** return the `nextLink` field at all when returning the last page of the collection. - -:no_entry: **DO NOT** return the `nextLink` field with a value of null. +:no_entry: **DO NOT** return the `nextLink` field with a value of null. - -:warning: **YOU SHOULD NOT** return a `count` of all objects in the collection as this may be expensive to compute. +:warning: **YOU SHOULD NOT** return a `count` of all objects in the collection as this may be expensive to compute. #### Query options - -:heavy_check_mark: **YOU MAY** support the following query parameters allowing customers to control the list operation: +:heavy_check_mark: **YOU MAY** support the following query parameters allowing customers to control the list operation: Parameter name | Type | Description ------------------- | ---- | ----------- @@ -682,30 +577,23 @@ Parameter name | Type | Description `select` | string array | a list of field names to be returned for each resource `expand` | string array | a list of the related resources to be included in line with each resource - -:white_check_mark: **DO** return an error if the client specifies any parameter not supported by the service. +:white_check_mark: **DO** return an error if the client specifies any parameter not supported by the service. - -:white_check_mark: **DO** treat these query parameter names as case-sensitive. +:white_check_mark: **DO** treat these query parameter names as case-sensitive. - -:white_check_mark: **DO** apply `select` or `expand` options after applying all the query options in the table above. +:white_check_mark: **DO** apply `select` or `expand` options after applying all the query options in the table above. - -:white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. +:white_check_mark: **DO** apply the query options to the collection in the order shown in the table above. - -:no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). +:no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). #### `filter` - -:heavy_check_mark: **YOU MAY** support `filter`ing of the results of a list operation with the `filter` query parameter. +:heavy_check_mark: **YOU MAY** support `filter`ing of the results of a list operation with the `filter` query parameter. The value of the `filter` option is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response. - -:white_check_mark: **DO** omit all resources from the collection for which the `filter` expression evaluates to false or to null, or references properties that are unavailable due to permissions. +:white_check_mark: **DO** omit all resources from the collection for which the `filter` expression evaluates to false or to null, or references properties that are unavailable due to permissions. Example: return all Products whose Price is less than $10.00 @@ -733,11 +621,9 @@ not | Logical negation | not price le 3.5 **Grouping Operators** | | ( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 - -:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#handling-errors) section if a client includes an operator in a `filter` expression that is not supported by the operation. +:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#handling-errors) section if a client includes an operator in a `filter` expression that is not supported by the operation. - -:white_check_mark: **DO** use the following operator precedence for supported operators when evaluating `filter` expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: +:white_check_mark: **DO** use the following operator precedence for supported operators when evaluating `filter` expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: | Group | Operator | Description | ----------------|----------|------------ @@ -752,8 +638,7 @@ not | Logical negation | not price le 3.5 | Conditional AND | and | Logical And | | Conditional OR | or | Logical Or | - -:heavy_check_mark: **YOU MAY** support orderby and `filter` functions such as concat and contains. For more information, see [odata Canonical Functions](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360979). +:heavy_check_mark: **YOU MAY** support orderby and `filter` functions such as concat and contains. For more information, see [odata Canonical Functions](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360979). ##### Operator examples The following examples illustrate the use and semantics of each of the logical operators. @@ -790,8 +675,7 @@ GET https://api.contoso.com/products?`filter`=(name eq 'Milk' or name eq 'Eggs') #### orderby - -:heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the `orderby` query parameter. +:heavy_check_mark: **YOU MAY** support sorting of the results of a list operation with the `orderby` query parameter. *NOTE: It is unusual for a service to support `orderby` because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results.* The value of the `orderby` parameter is a comma-separated list of expressions used to sort the items. @@ -799,20 +683,15 @@ A special case of such an expression is a property path terminating on a primiti Each expression in the `orderby` parameter value may include the suffix "asc" for ascending or "desc" for descending, separated from the expression by one or more spaces. - -:white_check_mark: **DO** sort the collection in ascending order on an expression if "asc" or "desc" is not specified. +:white_check_mark: **DO** sort the collection in ascending order on an expression if "asc" or "desc" is not specified. - -:white_check_mark: **DO** sort NULL values as "less than" non-NULL values. +:white_check_mark: **DO** sort NULL values as "less than" non-NULL values. - -:white_check_mark: **DO** sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on. +:white_check_mark: **DO** sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on. - -:white_check_mark: **DO** use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically. +:white_check_mark: **DO** use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically. - -:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#handling-errors) section if the client requests sorting by a field that is not supported by the operation. +:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#handling-errors) section if the client requests sorting by a field that is not supported by the operation. For example, to return all people sorted by name in ascending order: ```text @@ -832,39 +711,31 @@ will return all people whose name is David sorted in ascending order by hireDate ##### Considerations for sorting with pagination - -:white_check_mark: **DO** use the same `filter`ing options and sort order for all pages of a paginated list operation response. +:white_check_mark: **DO** use the same `filter`ing options and sort order for all pages of a paginated list operation response. ##### skip - -:white_check_mark: **DO** define the `skip` parameter as an integer with a default and minimum value of 0. +:white_check_mark: **DO** define the `skip` parameter as an integer with a default and minimum value of 0. - -:heavy_check_mark: **YOU MAY** allow clients to pass the `skip` query parameter to specify an offset into collection of the first resource to be returned. +:heavy_check_mark: **YOU MAY** allow clients to pass the `skip` query parameter to specify an offset into collection of the first resource to be returned. ##### top - - -:heavy_check_mark: **YOU MAY** allow clients to pass the `top` query parameter to specify the maximum number of resources to return from the collection. + +:heavy_check_mark: **YOU MAY** allow clients to pass the `top` query parameter to specify the maximum number of resources to return from the collection. If supporting `top`: :white_check_mark: **DO** define the `top` parameter as an integer with a minimum value of 1. If not specified, `top` has a default value of infinity. - -:white_check_mark: **DO** return the collection's `top` number of resources (if available), starting from `skip`. +:white_check_mark: **DO** return the collection's `top` number of resources (if available), starting from `skip`. ##### maxpagesize - -:heavy_check_mark: **YOU MAY** allow clients to pass the `maxpagesize` query parameter to specify the maximum number of resources to include in a single page response. +:heavy_check_mark: **YOU MAY** allow clients to pass the `maxpagesize` query parameter to specify the maximum number of resources to include in a single page response. - -:white_check_mark: **DO** define the `maxpagesize` parameter as an optional integer with a default value appropriate for the collection. +:white_check_mark: **DO** define the `maxpagesize` parameter as an optional integer with a default value appropriate for the collection. - -:white_check_mark: **DO** make clear in documentation of the `maxpagesize` parameter that the operation may choose to return fewer resources than the value specified. +:white_check_mark: **DO** make clear in documentation of the `maxpagesize` parameter that the operation may choose to return fewer resources than the value specified. - + ### API Versioning Azure services need to change over time. However, when changing a service, there are 2 requirements: @@ -873,37 +744,29 @@ Azure services need to change over time. However, when changing a service, there *NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy/) has tables (section 5) describing what kinds of changes are considered breaking. Breaking changes are allowable (due to security/compliance/etc.) if approved by the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) but only following ample communication to customers and a lengthy deprecation period.* - -:white_check_mark: **DO** review any API changes with the Azure API Stewardship Board +:white_check_mark: **DO** review any API changes with the Azure API Stewardship Board Clients specify the version of the API to be used in every request to the service, even requests to an `Operation-Location` or `nextLink` URL returned by the service. - -:white_check_mark: **DO** use a required query parameter named `api-version` on every operation for the client to specify the API version. +:white_check_mark: **DO** use a required query parameter named `api-version` on every operation for the client to specify the API version. - -:white_check_mark: **DO** use `YYYY-MM-DD` date values, with a `-preview` suffix for preview versions, as the valid values for `api-version`. +:white_check_mark: **DO** use `YYYY-MM-DD` date values, with a `-preview` suffix for preview versions, as the valid values for `api-version`. ```text PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 ``` - -:white_check_mark: **DO** use a later date for each new preview version +:white_check_mark: **DO** use a later date for each new preview version When releasing a new preview, the service team may completely retire any previous preview versions after giving customers at least 90 days to upgrade their code - -:no_entry: **DO NOT** introduce any breaking changes into the service. +:no_entry: **DO NOT** introduce any breaking changes into the service. - -:no_entry: **DO NOT** include a version number segment in any operation path. +:no_entry: **DO NOT** include a version number segment in any operation path. - -:no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview `api-version` is '2021-06-04-preview', the GA version of the API **must be** a date later than 2021-06-04 +:no_entry: **DO NOT** use the same date when transitioning from a preview API to a GA API. If the preview `api-version` is '2021-06-04-preview', the GA version of the API **must be** a date later than 2021-06-04 - -:no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. +:no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. #### Use Extensible Enums @@ -926,22 +789,19 @@ While removing a value from an enum is a breaking change, adding value to an enu } ``` - -:ballot_box_with_check: **You SHOULD** use extensible enums unless you are positive that the symbol set will **NEVER** change over time. +:ballot_box_with_check: **You SHOULD** use extensible enums unless you are positive that the symbol set will **NEVER** change over time. - + ### Deprecating Behavior Notification When the [API Versioning](#API-Versioning) guidance above cannot be followed and the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) approve a [breaking change](#123-definition-of-a-breaking-change) to a specific API version it must be communicated to its callers. The API version that is being deprecated must add the `azure-deprecating` response header with a semicolon-delimited string notifying the caller what is being deprecated, when it will no longer function, and a URL linking to more information such as what new operation they should use instead. The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation and use a newer API version or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. The string is _not_ part of an API contract (except for the semi-colon delimiters) and may be changed/improved at any time without incurring a breaking change. - -:white_check_mark: **DO** include the `azure-deprecating` header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. +:white_check_mark: **DO** include the `azure-deprecating` header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. > NOTE: We do not want to scare customers with this header. - -:white_check_mark: **DO** make the header's value a semicolon-delimited string indicating a set of deprecations where each one indicates what is deprecating, when it is deprecating, and a URL to more information. +:white_check_mark: **DO** make the header's value a semicolon-delimited string indicating a set of deprecations where each one indicates what is deprecating, when it is deprecating, and a URL to more information. Deprecations should use the following pattern: ```text @@ -960,22 +820,20 @@ For example: - `azure-deprecating: Model version 2021-01-15 used in Sentiment analysis will retire on 2022-12-01 (https://aka.ms/ta-modelversions?sentimentAnalysis)` - `azure-deprecating: TLS 1.0 & 1.1 support will retire on 2022-10-01 (https://devblogs.microsoft.com/devops/deprecating-weak-cryptographic-standards-tls-1-0-and-1-1-in-azure-devops-services/)` - -:no_entry: **DO NOT** introduce this header without approval from [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) and an official deprecation notice on [Azure Updates](https://azure.microsoft.com/updates/). +:no_entry: **DO NOT** introduce this header without approval from [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) and an official deprecation notice on [Azure Updates](https://azure.microsoft.com/updates/). - + ### Repeatability of requests The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. - -:ballot_box_with_check: **YOU SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). +:ballot_box_with_check: **YOU SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). - The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. - A service advertises support for repeatability requests by adding the `Repeatability-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. - When understood, all endpoints co-located behind a DNS name **MUST** understand the header. This means that a service **MUST NOT** ignore the presence of a header for any endpoints behind the DNS name, but rather fail the request containing a `Repeatability-Request-ID` header if that particular endpoint lacks support for repeatable requests. Such partial support **SHOULD** be avoided due to the confusion it causes for clients. - + ### Long-Running Operations & Jobs When the processing for an operation may take a significant amount of time to complete, it should be @@ -985,11 +843,9 @@ through another API call. See the [Long Running Operations section](./ConsiderationsForServiceDesign.md#long-running-operations) in Considerations for Service Design for an introduction to the design of long-running operations. - -:white_check_mark: **DO** implement an operation as an LRO if the 99th percentile response time is greater than 1s. +:white_check_mark: **DO** implement an operation as an LRO if the 99th percentile response time is greater than 1s. - -:no_entry: **DO NOT** implement PATCH as an LRO. If LRO update is required it must be implemented with POST. +:no_entry: **DO NOT** implement PATCH as an LRO. If LRO update is required it must be implemented with POST. In rare instances where an operation may take a _very long_ time to complete, e.g. longer than 15 minutes, it may be better to expose this as a first class resource of the API rather than as an operation on another resource. @@ -1005,81 +861,59 @@ These are described in the following two sections. A POST or DELETE long-running operation accepts a request from the client to initiate the operation processing and returns a [status monitor](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.3) that reports the operation's progress. - -:no_entry: **DO NOT** use a long-running POST to create a resource -- use PUT as described below. +:no_entry: **DO NOT** use a long-running POST to create a resource -- use PUT as described below. - -:white_check_mark: **DO** allow the client to pass an `Operation-Id` header with an ID for the operation's status monitor. +:white_check_mark: **DO** allow the client to pass an `Operation-Id` header with an ID for the operation's status monitor. - -:white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. +:white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. - -:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header matches an existing operation unless the request is identical to the prior request (a retry scenario). +:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header matches an existing operation unless the request is identical to the prior request (a retry scenario). - -:white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. +:white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. - -:white_check_mark: **DO** return a `202-Accepted` status code from the request that initiates an LRO if the processing of the operation was successfully initiated (except for "PUT with additional processing" type LRO). +:white_check_mark: **DO** return a `202-Accepted` status code from the request that initiates an LRO if the processing of the operation was successfully initiated (except for "PUT with additional processing" type LRO). - -:warning: **YOU SHOULD NOT** return any other `2xx` status code from the initial request of an LRO -- return `202-Accepted` and a status monitor even if processing was completed before the initiating request returns. +:warning: **YOU SHOULD NOT** return any other `2xx` status code from the initial request of an LRO -- return `202-Accepted` and a status monitor even if processing was completed before the initiating request returns. - -:white_check_mark: **DO** return a status monitor in the response body as described in [Obtaining status and results of long-running operations](#obtaining-status-and-results-of-long-running-operations). +:white_check_mark: **DO** return a status monitor in the response body as described in [Obtaining status and results of long-running operations](#obtaining-status-and-results-of-long-running-operations). - -:ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation. +:ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation. - -:ballot_box_with_check: **YOU SHOULD** include the `api-version` query parameter in the `Operation-Location` header with the same version passed on the initial request if it is required by the get operation on the status monitor. +:ballot_box_with_check: **YOU SHOULD** include the `api-version` query parameter in the `Operation-Location` header with the same version passed on the initial request if it is required by the get operation on the status monitor. #### PUT operation with additional long-running processing For a PUT (create or replace) with additional long-running processing: - -:white_check_mark: **DO** allow the client to pass an `Operation-Id` header with a ID for the status monitor for the operation. +:white_check_mark: **DO** allow the client to pass an `Operation-Id` header with a ID for the status monitor for the operation. - -:white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. +:white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. - -:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header that matches an existing operation unless the request is identical to the prior request (a retry scenario). +:white_check_mark: **DO** fail a request with a `400-BadRequest` if the `Operation-Id` header that matches an existing operation unless the request is identical to the prior request (a retry scenario). - -:white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. +:white_check_mark: **DO** perform as much validation as practical when initiating the operation to alert clients of errors early. - -:white_check_mark: **DO** return a `201-Created` status code for create or `200-OK` for replace from the initial request with a representation of the resource if the resource was created successfully. +:white_check_mark: **DO** return a `201-Created` status code for create or `200-OK` for replace from the initial request with a representation of the resource if the resource was created successfully. - -:white_check_mark: **DO** include an `Operation-Id` header in the response with the ID of the status monitor for the operation. +:white_check_mark: **DO** include an `Operation-Id` header in the response with the ID of the status monitor for the operation. - -:white_check_mark: **DO** include response headers with any additional values needed for a GET request to the status monitor (e.g. location). +:white_check_mark: **DO** include response headers with any additional values needed for a GET request to the status monitor (e.g. location). - -:ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation. +:ballot_box_with_check: **YOU SHOULD** include an `Operation-Location` header in the response with the absolute URL of the status monitor for the operation. - -:ballot_box_with_check: **YOU SHOULD** include the `api-version` query parameter in the `Operation-Location` header with the same version passed on the initial request if it is required by the get operation on the status monitor. +:ballot_box_with_check: **YOU SHOULD** include the `api-version` query parameter in the `Operation-Location` header with the same version passed on the initial request if it is required by the get operation on the status monitor. #### Obtaining status and results of long-running operations For all long-running operations, the client will issue a GET on a status monitor resource to obtain the current status of the operation. - -:white_check_mark: **DO** support the GET method on the status monitor endpoint that returns a `200-OK` response with the current state of the status monitor. +:white_check_mark: **DO** support the GET method on the status monitor endpoint that returns a `200-OK` response with the current state of the status monitor. - -:ballot_box_with_check: **YOU SHOULD** allow any valid value of the `api-version` query parameter to be used in the get operation on the status monitor. +:ballot_box_with_check: **YOU SHOULD** allow any valid value of the `api-version` query parameter to be used in the get operation on the status monitor. Note: Clients may replace the value of `api-version` in the `Operation-Location` URI with a value appropriate for their application. - -:white_check_mark: **DO** return a status monitor in the response body that conforms with the following structure: +:white_check_mark: **DO** return a status monitor in the response body that conforms with the following structure: **OperationStatus** : Object @@ -1091,22 +925,17 @@ Property | Type | Required | Description `result` | object | | Only for POST action-type LRO, the results of the operation when completed successfully additional
    properties | | | Additional named or dynamic properties of the operation - -:white_check_mark: **DO** include the `id` of the operation and any other values needed for the client to form a GET request to the status monitor (e.g. a `location` path parameter). +:white_check_mark: **DO** include the `id` of the operation and any other values needed for the client to form a GET request to the status monitor (e.g. a `location` path parameter). - -:white_check_mark: **DO** include a `Retry-After` header in the response to GET requests to the status monitor if the operation is not complete. The value of this header should be an integer number of seconds to wait before making the next request to the status monitor. +:white_check_mark: **DO** include a `Retry-After` header in the response to GET requests to the status monitor if the operation is not complete. The value of this header should be an integer number of seconds to wait before making the next request to the status monitor. - -:white_check_mark: **DO** include the `result` property (if any) in the status monitor for a POST action-type long-running operation when the operation completes successfully. +:white_check_mark: **DO** include the `result` property (if any) in the status monitor for a POST action-type long-running operation when the operation completes successfully. - -:no_entry: **DO NOT** include a `result` property in the status monitor for a long-running operation that is not a POST action-type long-running operation. +:no_entry: **DO NOT** include a `result` property in the status monitor for a long-running operation that is not a POST action-type long-running operation. - -:white_check_mark: **DO** retain the status monitor resource for some publicly documented period of time (at least 24 hours) after the operation completes. +:white_check_mark: **DO** retain the status monitor resource for some publicly documented period of time (at least 24 hours) after the operation completes. - + ### Bring your own Storage (BYOS) Many services need to store and retrieve data files. For this scenario, the service should not implement its own storage APIs and should instead leverage the existing Azure Storage service. When doing this, the customer @@ -1114,30 +943,23 @@ storage APIs and should instead leverage the existing Azure Storage service. Whe While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS provides the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders. - -:white_check_mark: **DO** use the Bring Your Own Storage pattern. +:white_check_mark: **DO** use the Bring Your Own Storage pattern. - -:white_check_mark: **DO** use a blob prefix for a logical folder (avoid terms such as ```directory```, ```folder```, or ```path```). +:white_check_mark: **DO** use a blob prefix for a logical folder (avoid terms such as ```directory```, ```folder```, or ```path```). - -:no_entry: **DO NOT** require a fresh container per operation. +:no_entry: **DO NOT** require a fresh container per operation. - -:white_check_mark: **DO** use managed identity and Role Based Access Control ([RBAC](https://docs.microsoft.com/azure/role-based-access-control/overview)) as the mechanism allowing customers to grant permission to their Storage account to your service. +:white_check_mark: **DO** use managed identity and Role Based Access Control ([RBAC](https://docs.microsoft.com/azure/role-based-access-control/overview)) as the mechanism allowing customers to grant permission to their Storage account to your service. - -:white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions. +:white_check_mark: **DO** Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions. - -:white_check_mark: **DO** Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. +:white_check_mark: **DO** Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change. #### Handling 'downstream' errors It is not uncommon to rely on other services, e.g. storage, when implementing your service. Inevitably, the services you depend on will fail. In these situations, you can include the downstream error code and text in the inner-error of the response body. This provides a consistent pattern for handling errors in the services you depend upon. - -:white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. +:white_check_mark: **DO** include error from downstream services as the 'inner-error' section of the response body. #### Working with files Generally speaking, there are two patterns that you will encounter when working with files; single file access, and file collections. @@ -1145,30 +967,22 @@ Generally speaking, there are two patterns that you will encounter when working ##### Single file access Designing an API for accessing a single file, depending on your scenario, is relatively straight forward. - -:heavy_check_mark: **YOU MAY** use a Shared Access Signature [SAS](https://docs.microsoft.com/azure/storage/common/storage-sas-overview) to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. +:heavy_check_mark: **YOU MAY** use a Shared Access Signature [SAS](https://docs.microsoft.com/azure/storage/common/storage-sas-overview) to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC. - -:ballot_box_with_check: **YOU SHOULD** if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text. +:ballot_box_with_check: **YOU SHOULD** if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text. - -:white_check_mark: **DO** return an HTTP status code representing the result of your service operation's behavior. +:white_check_mark: **DO** return an HTTP status code representing the result of your service operation's behavior. - -:white_check_mark: **DO** include the Storage error information in the 'inner-error' section of an error response if the error was the result of an internal Storage operation failure. This helps the client determine the underlying cause of the error, e.g.: a missing storage object or insufficient permissions. +:white_check_mark: **DO** include the Storage error information in the 'inner-error' section of an error response if the error was the result of an internal Storage operation failure. This helps the client determine the underlying cause of the error, e.g.: a missing storage object or insufficient permissions. - -:white_check_mark: **DO** allow the customer to specify a URL path to a single Storage object if your service requires access to a single file. +:white_check_mark: **DO** allow the customer to specify a URL path to a single Storage object if your service requires access to a single file. - -:heavy_check_mark: **YOU MAY** allow the customer to provide a [last-modified](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2) timestamp (in RFC1123 format) for read-only files. This allows the client to specify exactly which version of the files your service should use. +:heavy_check_mark: **YOU MAY** allow the customer to provide a [last-modified](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2) timestamp (in RFC1123 format) for read-only files. This allows the client to specify exactly which version of the files your service should use. When reading a file, your service passes this timestamp to Azure Storage using the [if-unmodified-since](https://datatracker.ietf.org/doc/html/rfc7232#section-3.4) request header. If the Storage operation fails with 412, the Storage object was modified and your service operation should return an appropriate 4xx status code and return the Storage error in your operation's 'inner-error' (see guideline above). - -:white_check_mark: **DO** allow the customer to specify a URL path to a logical folder (via prefix and delimiter) if your service requires access to multiple files (within this folder). For more information, see [List Blobs API](https://docs.microsoft.com/rest/api/storageservices/list-blobs) +:white_check_mark: **DO** allow the customer to specify a URL path to a logical folder (via prefix and delimiter) if your service requires access to multiple files (within this folder). For more information, see [List Blobs API](https://docs.microsoft.com/rest/api/storageservices/list-blobs) - -:heavy_check_mark: **YOU MAY** offer an `extensions` field representing an array of strings indicating file extensions of desired blobs within the logical folder. +:heavy_check_mark: **YOU MAY** offer an `extensions` field representing an array of strings indicating file extensions of desired blobs within the logical folder. A common pattern when working with multiple files is for your service to receive requests that contain the location(s) of files to process ("input") and a location(s) to place any files that result from processing ("output"). Note: the terms "input" and "output" are just examples; use terms more appropriate to your service's domain. @@ -1191,29 +1005,23 @@ For example, a service's request body to configure BYOS may look like this: Depending on the requirements of the service, there can be any number of "input" and "output" sections, including none. - -:white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter". For "location", the customer must pass a URL to a blob prefix which represents a directory. For "delimiter", the customer must specify the delimiter character they desire to use in the location URL; typically "/" or "\". +:white_check_mark: **DO** include a JSON object that has string values for "location" and "delimiter". For "location", the customer must pass a URL to a blob prefix which represents a directory. For "delimiter", the customer must specify the delimiter character they desire to use in the location URL; typically "/" or "\". - -:heavy_check_mark: **YOU MAY** support the "lastModified" field for input directories (see guideline above). +:heavy_check_mark: **YOU MAY** support the "lastModified" field for input directories (see guideline above). - -:white_check_mark: **DO** support a "location" URL with a container-scoped SAS that has a minimum of `listing` and `read` permissions for input directories. +:white_check_mark: **DO** support a "location" URL with a container-scoped SAS that has a minimum of `listing` and `read` permissions for input directories. - -:white_check_mark: **DO** support a "location" URL with a container-scoped SAS that has a minimum of `write` permissions for output directories. +:white_check_mark: **DO** support a "location" URL with a container-scoped SAS that has a minimum of `write` permissions for output directories. - + ### Conditional Requests When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an `ETag` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. An `ETag` is always set by the service and will enable you to _conditionally_ control how your service responds to requests, enabling you to provide predictable updates and more efficient access. - -:ballot_box_with_check: **YOU SHOULD** return an `ETag` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). +:ballot_box_with_check: **YOU SHOULD** return an `ETag` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). - -:ballot_box_with_check: **YOU SHOULD** use `ETag`s consistently across your API, i.e. if you use an `ETag`, accept it on all other operations. +:ballot_box_with_check: **YOU SHOULD** use `ETag`s consistently across your API, i.e. if you use an `ETag`, accept it on all other operations. You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). @@ -1224,13 +1032,11 @@ Implementing this strategy is relatively straightforward. First, you will return In these scenarios, when a request is made by the client an `ETag` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The `ETag` value can then be sent in subsequent requests as part of the `If-None-Match` header. This tells the service to compare the `ETag` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated `ETag` value in the header. - -:ballot_box_with_check: **YOU SHOULD** implement conditional read strategies +:ballot_box_with_check: **YOU SHOULD** implement conditional read strategies When supporting conditional read strategies: - -:white_check_mark: **DO** adhere to the following table for guidance: +:white_check_mark: **DO** adhere to the following table for guidance: | GET Request | Return code | Response | |:------------|:------------|:--------------------------------------------| @@ -1243,13 +1049,11 @@ For more control over caching, please refer to the `cache-control` [HTTP header] An `ETag` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of `ETags` and the [HTTP Request / Response Pattern](#http-request--response-pattern). - -:warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. +:warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. When supporting optimistic concurrency: - -:white_check_mark: **DO** adhere to the following table for guidance: +:white_check_mark: **DO** adhere to the following table for guidance: | Operation | Header | Value | ETag check | Return code | Response | |:------------|:--------------|:------|:-----------|:------------|----------------| @@ -1263,36 +1067,28 @@ When supporting optimistic concurrency: #### Computing ETags The strategy that you use to compute the `ETag` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the `ETag`. Another common strategy for determining the value of an `ETag` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. - -:ballot_box_with_check: **YOU SHOULD** use a hash of the representation of a resource rather than a last modified/version number +:ballot_box_with_check: **YOU SHOULD** use a hash of the representation of a resource rather than a last modified/version number While it may be tempting to use a revision/version number for the resource as the ETag, it interferes with client's ability to retry update requests. If a client sends a conditional update request, the service acts on the request, but the client never receives a response, a subsequent identical update will be seen as a conflict even though the retried request is attempting to make the same update. - -:ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. +:ballot_box_with_check: **YOU SHOULD**, if using a hash strategy, hash the entire resource. - -:ballot_box_with_check: **YOU SHOULD**, if supporting range requests, use a strong ETag in order to support caching. +:ballot_box_with_check: **YOU SHOULD**, if supporting range requests, use a strong ETag in order to support caching. - -:heavy_check_mark: **YOU MAY** use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. +:heavy_check_mark: **YOU MAY** use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds. - -:heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. +:heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. - -:white_check_box: **DO**, when supporting multiple representations (e.g. Content-Encodings) for the same resource, generate different ETag values for the different representations. +:white_check_box: **DO**, when supporting multiple representations (e.g. Content-Encodings) for the same resource, generate different ETag values for the different representations. - + ### Distributed Tracing & Telemetry Azure SDK client guidelines specify that client libraries must send telemetry data through the `User-Agent` header, `X-MS-UserAgent` header, and Open Telemetry. Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. - -:white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. +:white_check_mark: **DO** follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry. - -:no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. +:no_entry: **DO NOT** reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers. **Additional References** - [Azure SDK client guidelines](https://azure.github.io/azure-sdk/general_azurecore.html) From bae0b0e5f081dfe1d9a6139eb3bdb7288bf1c81e Mon Sep 17 00:00:00 2001 From: Michael Pizzo Date: Wed, 19 Apr 2023 12:05:57 -0700 Subject: [PATCH 526/729] Fix Dictionary guidance to use PATCH to add/remove/update properties. --- graph/patterns/dictionary.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 77a2ef2f..3f6d2507 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -10,9 +10,9 @@ The API design requires a resource to include an unknown quantity of data elemen ## Solution -API designers use a JSON object to represent a dictionary in an `application/json` response payload. When describing the model in CSDL, a new complex type can be created that derives from `Org.OData.Core.V1.Dictionary` and then uses the `Org.OData.Validation.V1.OpenPropertyTypeConstraint` to constrain the type that can be used for the values in the dictionary. +API designers use a JSON object to represent a dictionary in an `application/json` response payload. When describing the model in CSDL, a new complex type can be created that derives from `Org.OData.Core.V1.Dictionary` and then uses the `Org.OData.Validation.V1.OpenPropertyTypeConstraint` to constrain the type that can be used for the values in the dictionary as appropriate. -Dictionary entries can be added via `POST`, updated via `PATCH`, and removed by setting the entry value to `null`. Multiple entries can be updated at the same time by using `PATCH` on the dictionary property. +Dictionary values can be added, removed, or modified via `PATCH` to the dictionary property. Values are removed by setting the property to `null`. ## When to use this pattern @@ -36,8 +36,9 @@ Because dictionary entries are removed by setting the value to `null`, dictionar Open questions: - Can/should PUT be supported on the dictionary property and/or the entry value? -- What does OData say about being able to POST to a structured property? Will OData Web API allow that? - Must an implementer support PATCH at both the dictionary level and the entry level? +- Should we also allow DELETE to a property to be equivalent to setting to null? +- Why do we not allow mixed primitives or mixed primitive/complex typed values? what about collections? For more information, see the [OData reference](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md#dictionary). @@ -130,7 +131,7 @@ Response: #### Create an entry in the dictionary ```HTTP -POST https://graph.microsoft.com/v1.0/users/10/roles/author +PATCH https://graph.microsoft.com/v1.0/users/10/roles/author { "domain": "contoso" @@ -175,7 +176,10 @@ PATCH https://graph.microsoft.com/v1.0/users/10/roles/author #### Delete an entry from the dictionary ```HTTP -DELETE https://graph.microsoft.com/v1.0/users/10/roles/author +PATCH https://graph.microsoft.com/v1.0/users/10/roles/author +{ + "domain": null +} ``` ### CDSL example @@ -200,8 +204,6 @@ The following example defines a complex type **roleSettings** as well as a dicti GET PATCH - DELETE - POST
    From 3b8cc2862a7ca9dbd27c54e495d8b306d4d8d7bf Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 12 May 2023 09:03:40 -0700 Subject: [PATCH 527/729] Update Guidelines.md Explain service response for missing/unsupported `api-version` --- azure/Guidelines.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a7fd7b9f..623cf005 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -16,6 +16,7 @@ Please ensure that you add an anchor tag to any new guidelines that you add and | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2023-May-12 | Explain service response for missing/unsupported `api-version` | | 2023-Apr-07 | Update/clarify guidelines on polymorphism | | 2022-Sep-07 | Updated URL guidelines for DNS Done Right | | 2022-Jul-15 | Update guidance on long-running operations | @@ -752,6 +753,10 @@ Clients specify the version of the API to be used in every request to the servic :white_check_mark: **DO** use `YYYY-MM-DD` date values, with a `-preview` suffix for preview versions, as the valid values for `api-version`. +:white_check_mark: **DO** return HTTP 400 with error code "MissingApiVersionParameter" and message "The api-version query parameter (?api-version=) is required for all requests" if client omits the `api-version` query parameter. + +:white_check_mark: **DO** return HTTP 400 with error code "UnsupportedApiVersionValue" and message "Unsupported api-version '{0}'. The supported api-versions are '{1}'." if client passes an `api-version` value unrecognized by the service. For the supported api-versions, just list all the stable versions still supported by the service and just the latest public preview version (if any). + ```text PUT https://service.azure.com/users/Jeff?api-version=2021-06-04 ``` From b7e0d95428e0ea7d4c58cfe0263dc6da86b8c8c4 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 25 Jun 2023 21:01:34 -0500 Subject: [PATCH 528/729] Restructure and strengthen guidance on conditional requests --- azure/ConsiderationsForServiceDesign.md | 31 +++++++++++++- azure/Guidelines.md | 55 +++++++++++++------------ 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index a7799662..245d83d3 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -172,7 +172,7 @@ Before releasing your API plan to invest significant design effort, get customer As your service evolves over time, it will be natural that you want to remove operations that are no longer needed. For example, additional requirements or new capability in your service, may have resulted in a new operation that, effectively, replaces an old one. Azure has a well established breaking changes policy that describes how to approach these kinds of changes. As part of this policy, the service team is required to clearly communicate to customers when their API is changing, e.g. deprecating operations. Often, this is done via an email to the address that is attached to the Azure subscription. -However, given how many organizations are structured, it's common that this email address is different from the actual people writing code against your API. To address this, the service API should declare that it may return the `azure-deprecating` header, to indicate that this operation will be removed in the future. There is a simple string convention, specifed in the [Azure REST API Guidelines](https://aka.ms/azapi/guidelines) that provides more information about the forthcoming deprecation. +However, given how many organizations are structured, it's common that this email address is different from the actual people writing code against your API. To address this, the service API should declare that it may return the `azure-deprecating` header, to indicate that this operation will be removed in the future. There is a simple string convention, specified in the [Azure REST API Guidelines](https://aka.ms/azapi/guidelines) that provides more information about the forthcoming deprecation. This header is targeted at developers or operation professionals, and it is intended to give them enough information and lead time to properly adapt to this change. Your documentation should reference this header and encourage logging and alerting practices based on its presence. ## Avoid Surprises @@ -234,7 +234,7 @@ PATCH must never be used for long-running operations -- it should be reserved fo If a long-running update is required it should be implemented with POST. There is a special form of long-running operation initiated with PUT that is described -in [Create (PUT) with additional long-running processing](#create-put-with-additional-long-running-processing). +in [Create (PUT) with additional long-running processing](./Guidelines.md#put-operation-with-additional-long-running-processing). The remainder of this section describes the pattern for long-running POST and DELETE operations. This diagram illustrates how a long-running operation with a status monitor is initiated and then how the client @@ -492,6 +492,33 @@ and the number of results to return, respectively. Note that when `top` specifies a value larger than the server-driven paging page size, the response will be paged accordingly. +## Conditional Requests + +When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. +Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. +Both of these scenarios can be accomplished with conditional requests, where the client specifies a _precondition_ +for execution of a request, based on its last modification date or entity tag ("ETag"). +An Etag identifies a 'version' or 'instance' of a resource and is computed by the service and returned in an `ETag` response header for GET or other operations on the resource. + +### Cache Control + +One of the more common uses for conditional requests is cache control. This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). +A client can make a "conditional GET request" for the resource, with a precondition header that requests that +data be returned only when the version on the service does not match the Etag or last modified date in the header. +If there are no changes, then there is no need to return the resource, as the client already has the most recent version. + +Implementing this strategy is relatively straightforward. First, you will return an `ETag` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](./Guidelines.md#computing-etags) section provides guidance on how to properly calculate the value of your `ETag`. +In these scenarios, when a request is made by the client an `ETag` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The `ETag` value can then be sent in subsequent requests as part of the `If-None-Match` header. +This tells the service to compare the `ETag` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated `ETag` value in the header. + +### Optimistic Concurrency + +Optimistic concurrency is a strategy used in HTTP to avoid the "lost update" problem that can occur when multiple clients attempt to update a resource simultaneously. +Clients can use ETags returned by the service to specify a _precondition_ for the execution of an update, to ensure that the resource has not been updated since the client last observed it. +For example, the client can specify an `If-Match` header with the last ETag value received by the client in an update request. +The service processes the update only if the Etag value in the header matches the ETag of the current resource on the server. +By computing and returning ETags for your resources, you enable clients to avoid using a "pessimistic" strategy where the "last write always wins." + ## Getting Help: The Azure REST API Stewardship Board The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 623cf005..439852c3 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -799,7 +799,7 @@ While removing a value from an enum is a breaking change, adding value to an enu ### Deprecating Behavior Notification -When the [API Versioning](#API-Versioning) guidance above cannot be followed and the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) approve a [breaking change](#123-definition-of-a-breaking-change) to a specific API version it must be communicated to its callers. The API version that is being deprecated must add the `azure-deprecating` response header with a semicolon-delimited string notifying the caller what is being deprecated, when it will no longer function, and a URL linking to more information such as what new operation they should use instead. +When the [API Versioning](#api-versioning) guidance above cannot be followed and the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) approve a breaking change to a specific API version it must be communicated to its callers. The API version that is being deprecated must add the `azure-deprecating` response header with a semicolon-delimited string notifying the caller what is being deprecated, when it will no longer function, and a URL linking to more information such as what new operation they should use instead. The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation and use a newer API version or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. The string is _not_ part of an API contract (except for the semi-colon delimiters) and may be changed/improved at any time without incurring a breaking change. @@ -1020,28 +1020,38 @@ Depending on the requirements of the service, there can be any number of "input" ### Conditional Requests -When designing an API, you will almost certainly have to manage how your resource is updated. For example, if your resource is a bank account, you will want to ensure that one transaction--say depositing money--does not overwrite a previous transaction. -Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. To enable this level of control, services should leverage an `ETag` header, or "entity tag," which will identify the 'version' or 'instance' of the resource a particular client is working with. -An `ETag` is always set by the service and will enable you to _conditionally_ control how your service responds to requests, enabling you to provide predictable updates and more efficient access. -:ballot_box_with_check: **YOU SHOULD** return an `ETag` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). +The [HTTP Standard][] defines request headers that clients may use to specify a _precondition_ +for execution of an operation. These headers allow clients to implement efficient caching mechanisms +and avoid data loss in the event of concurrent updates to a resource. The headers that specify conditional execution are "If-Match", "If-None-Match", "If-Modified-Since", "If-Unmodified-Since", and "If-Range". + +[HTTP Standard]: https://datatracker.ietf.org/doc/html/rfc9110 + + + + + + + +:white_check_mark: **DO** honor any precondition headers received as part of a client request. -:ballot_box_with_check: **YOU SHOULD** use `ETag`s consistently across your API, i.e. if you use an `ETag`, accept it on all other operations. +The HTTP Standard does not allow precondition headers to be ignored, as it can be unsafe to do so. -You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232). +:white_check_mark: **DO** return the appropriate precondition failed error response if the service cannot verify the truth of the precondition. -#### Cache Control -One of the more common uses for `ETag` headers is cache control, also referred to a "conditional GET." This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). That is, using the value of the `ETag` , the server can determine if the resource has changed. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. +While conditional requests can be implemented using last modified dates, entity tags ("ETags") are strongly +preferred since last modified dates cannot distinguish updates made less than a second apart. + +:ballot_box_with_check: **YOU SHOULD** return an `ETag` with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not). -Implementing this strategy is relatively straightforward. First, you will return an `ETag` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](#computing-etags) section provides guidance on how to properly calculate the value of your `ETag`. -In these scenarios, when a request is made by the client an `ETag` header is returned, with a value that uniquely identifies that specific instance (or version) of the resource. The `ETag` value can then be sent in subsequent requests as part of the `If-None-Match` header. -This tells the service to compare the `ETag` that came in with the request, with the latest value that it has calculated. If the two values are the same, then it is not necessary to return the resource to the client--it already has it. If they are different, then the service will return the latest version of the resource, along with the updated `ETag` value in the header. +#### Conditional Request behavior -:ballot_box_with_check: **YOU SHOULD** implement conditional read strategies +This section gives a summary of the processing to perform for conditional headers. +See the [Conditional Requests section of the HTTP Standard][] for details on how and when to evaluate these headers. -When supporting conditional read strategies: +[Conditional Requests section of the HTTP Standard]: https://datatracker.ietf.org/doc/html/rfc9110#name-conditional-requests -:white_check_mark: **DO** adhere to the following table for guidance: +:white_check_mark: **DO** adhere to the following table for processing a GET request with conditional headers: | GET Request | Return code | Response | |:------------|:------------|:--------------------------------------------| @@ -1050,15 +1060,7 @@ When supporting conditional read strategies: For more control over caching, please refer to the `cache-control` [HTTP header](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control). -#### Optimistic Concurrency -An `ETag` should also be used to reflect the create, update, and delete policies of your service. Specifically, you should avoid a "pessimistic" strategy where the 'last write always wins." These can be expensive to build and scale because avoiding the "lost update" problem often requires sophisticated concurrency controls. -Instead, implement an "optimistic concurrency" strategy, where the incoming state of the resource is first compared against what currently resides in the service. Optimistic concurrency strategies are implemented through the combination of `ETags` and the [HTTP Request / Response Pattern](#http-request--response-pattern). - -:warning: **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins. - -When supporting optimistic concurrency: - -:white_check_mark: **DO** adhere to the following table for guidance: +:white_check_mark: **DO** adhere to the following table for processing a PUT, PATCH, or DELETE request with conditional headers: | Operation | Header | Value | ETag check | Return code | Response | |:------------|:--------------|:------|:-----------|:------------|----------------| @@ -1069,7 +1071,8 @@ When supporting optimistic concurrency: | DELETE | `If-Match` | value of ETag | value matches the latest value on the server | `204-No Content` | Response body SHOULD be empty. | | DELETE | `If-Match` | value of ETag | value does NOT match the latest value on the server | `412-Preconditioned Failed` | Response body SHOULD be empty.| -#### Computing ETags +#### ETags + The strategy that you use to compute the `ETag` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the `ETag`. Another common strategy for determining the value of an `ETag` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. :ballot_box_with_check: **YOU SHOULD** use a hash of the representation of a resource rather than a last modified/version number @@ -1084,7 +1087,7 @@ While it may be tempting to use a revision/version number for the resource as th :heavy_check_mark: **YOU MAY** consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash. -:white_check_box: **DO**, when supporting multiple representations (e.g. Content-Encodings) for the same resource, generate different ETag values for the different representations. +:white_check_mark: **DO**, when supporting multiple representations (e.g. Content-Encodings) for the same resource, generate different ETag values for the different representations. ### Distributed Tracing & Telemetry From eb31f82d3eb1ee48b3b3735a86bf609004957445 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin Date: Mon, 26 Jun 2023 18:02:25 -0700 Subject: [PATCH 529/729] Remove unsupported and non-existent annotation from dictionary example --- graph/patterns/dictionary.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 77a2ef2f..6e7811f8 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -196,14 +196,6 @@ The following example defines a complex type **roleSettings** as well as a dicti microsoft.graph.roleSettings - - - GET - PATCH - DELETE - POST - -
    ``` From c6ff30f32b4d980596c7f39e832559bbe02c8576 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Tue, 27 Jun 2023 10:59:23 -0500 Subject: [PATCH 530/729] Address PR review comments --- azure/ConsiderationsForServiceDesign.md | 3 ++- azure/Guidelines.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 245d83d3..df01ffef 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -148,6 +148,7 @@ The following are recommended names for properties that match the associated des | lastModifiedAt | The date and time the resource was last modified. | | deletedAt | The date and time the resource was deleted. | | kind | The discriminator value for a polymorphic resource | +| etag | The entity tag used for optimistic concurrency control, when included as a property of a resource. | ### `name` vs `id` @@ -517,7 +518,7 @@ Optimistic concurrency is a strategy used in HTTP to avoid the "lost update" pro Clients can use ETags returned by the service to specify a _precondition_ for the execution of an update, to ensure that the resource has not been updated since the client last observed it. For example, the client can specify an `If-Match` header with the last ETag value received by the client in an update request. The service processes the update only if the Etag value in the header matches the ETag of the current resource on the server. -By computing and returning ETags for your resources, you enable clients to avoid using a "pessimistic" strategy where the "last write always wins." +By computing and returning ETags for your resources, you enable clients to avoid using a strategy where the "last write always wins." ## Getting Help: The Azure REST API Stewardship Board The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 439852c3..663dd21c 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1071,7 +1071,7 @@ For more control over caching, please refer to the `cache-control` [HTTP header] | DELETE | `If-Match` | value of ETag | value matches the latest value on the server | `204-No Content` | Response body SHOULD be empty. | | DELETE | `If-Match` | value of ETag | value does NOT match the latest value on the server | `412-Preconditioned Failed` | Response body SHOULD be empty.| -#### ETags +#### Computing ETags The strategy that you use to compute the `ETag` depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the `ETag`. Another common strategy for determining the value of an `ETag` is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism. From 820442f92c9e681518131069203f33e297b3b76a Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 28 Jun 2023 08:33:04 -0500 Subject: [PATCH 531/729] Address feedback on API Stewardship LT meeting --- azure/Guidelines.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 663dd21c..91c23fc0 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1039,6 +1039,8 @@ The HTTP Standard does not allow precondition headers to be ignored, as it can b :white_check_mark: **DO** return the appropriate precondition failed error response if the service cannot verify the truth of the precondition. +Note: A GA service that currently ignores precondition headers should not switch to honoring them as this could break production applications. This change should only be made prior to GA for the service or as part of a formal breaking change. + While conditional requests can be implemented using last modified dates, entity tags ("ETags") are strongly preferred since last modified dates cannot distinguish updates made less than a second apart. @@ -1046,12 +1048,12 @@ preferred since last modified dates cannot distinguish updates made less than a #### Conditional Request behavior -This section gives a summary of the processing to perform for conditional headers. +This section gives a summary of the processing to perform for precondition headers. See the [Conditional Requests section of the HTTP Standard][] for details on how and when to evaluate these headers. [Conditional Requests section of the HTTP Standard]: https://datatracker.ietf.org/doc/html/rfc9110#name-conditional-requests -:white_check_mark: **DO** adhere to the following table for processing a GET request with conditional headers: +:white_check_mark: **DO** adhere to the following table for processing a GET request with precondition headers: | GET Request | Return code | Response | |:------------|:------------|:--------------------------------------------| @@ -1060,7 +1062,7 @@ See the [Conditional Requests section of the HTTP Standard][] for details on how For more control over caching, please refer to the `cache-control` [HTTP header](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control). -:white_check_mark: **DO** adhere to the following table for processing a PUT, PATCH, or DELETE request with conditional headers: +:white_check_mark: **DO** adhere to the following table for processing a PUT, PATCH, or DELETE request with precondition headers: | Operation | Header | Value | ETag check | Return code | Response | |:------------|:--------------|:------|:-----------|:------------|----------------| From f635b340fa71a651089e560cd649c56e1527a3dc Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 28 Jun 2023 21:31:34 -0500 Subject: [PATCH 532/729] Reversed position on GA services switching to honor precondition headers --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 91c23fc0..1d67b3ba 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1039,7 +1039,7 @@ The HTTP Standard does not allow precondition headers to be ignored, as it can b :white_check_mark: **DO** return the appropriate precondition failed error response if the service cannot verify the truth of the precondition. -Note: A GA service that currently ignores precondition headers should not switch to honoring them as this could break production applications. This change should only be made prior to GA for the service or as part of a formal breaking change. +Note: The Azure Breaking Changes review board will allow a GA service that currently ignores precondition headers to begin honoring them in a new API version without a formal breaking change notification. The potential for disruption to customer applications is low and outweighed by the value of conforming to HTTP standards. While conditional requests can be implemented using last modified dates, entity tags ("ETags") are strongly preferred since last modified dates cannot distinguish updates made less than a second apart. From 2f9b24644d5190ff4e67cfa14aea4bd1f3434fef Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 29 Jun 2023 13:25:51 -0500 Subject: [PATCH 533/729] Apply suggestions from PR review Co-authored-by: Heath Stewart --- azure/ConsiderationsForServiceDesign.md | 2 +- azure/Guidelines.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index df01ffef..3f78e08d 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -173,7 +173,7 @@ Before releasing your API plan to invest significant design effort, get customer As your service evolves over time, it will be natural that you want to remove operations that are no longer needed. For example, additional requirements or new capability in your service, may have resulted in a new operation that, effectively, replaces an old one. Azure has a well established breaking changes policy that describes how to approach these kinds of changes. As part of this policy, the service team is required to clearly communicate to customers when their API is changing, e.g. deprecating operations. Often, this is done via an email to the address that is attached to the Azure subscription. -However, given how many organizations are structured, it's common that this email address is different from the actual people writing code against your API. To address this, the service API should declare that it may return the `azure-deprecating` header, to indicate that this operation will be removed in the future. There is a simple string convention, specified in the [Azure REST API Guidelines](https://aka.ms/azapi/guidelines) that provides more information about the forthcoming deprecation. +However, given how many organizations are structured, it's common that this email address is different from the actual people writing code against your API. To address this, the service API should declare that it may return the `azure-deprecating` header, to indicate that this operation will be removed in the future. There is a simple string convention, specified in the [Azure REST API Guidelines](https://aka.ms/azapi/guidelines#deprecating-behavior-notification) that provides more information about the forthcoming deprecation. This header is targeted at developers or operation professionals, and it is intended to give them enough information and lead time to properly adapt to this change. Your documentation should reference this header and encourage logging and alerting practices based on its presence. ## Avoid Surprises diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 1d67b3ba..2efaf0d0 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1023,7 +1023,7 @@ Depending on the requirements of the service, there can be any number of "input" The [HTTP Standard][] defines request headers that clients may use to specify a _precondition_ for execution of an operation. These headers allow clients to implement efficient caching mechanisms -and avoid data loss in the event of concurrent updates to a resource. The headers that specify conditional execution are "If-Match", "If-None-Match", "If-Modified-Since", "If-Unmodified-Since", and "If-Range". +and avoid data loss in the event of concurrent updates to a resource. The headers that specify conditional execution are `If-Match`, `If-None-Match`, `If-Modified-Since`, `If-Unmodified-Since`, and `If-Range`. [HTTP Standard]: https://datatracker.ietf.org/doc/html/rfc9110 From 7bfc7844d3ac17c869bd45ecfaa05c9598497005 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 29 Jun 2023 13:30:05 -0500 Subject: [PATCH 534/729] More updates for PR comments --- azure/ConsiderationsForServiceDesign.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 3f78e08d..15d5e15b 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -499,13 +499,13 @@ When designing an API, you will almost certainly have to manage how your resourc Similarly, it could be very expensive to send a resource to a client. This could be because of its size, network conditions, or a myriad of other reasons. Both of these scenarios can be accomplished with conditional requests, where the client specifies a _precondition_ for execution of a request, based on its last modification date or entity tag ("ETag"). -An Etag identifies a 'version' or 'instance' of a resource and is computed by the service and returned in an `ETag` response header for GET or other operations on the resource. +An ETag identifies a 'version' or 'instance' of a resource and is computed by the service and returned in an `ETag` response header for GET or other operations on the resource. ### Cache Control One of the more common uses for conditional requests is cache control. This is especially useful when resources are large in size, expensive to compute/calculate, or hard to reach (significant network latency). A client can make a "conditional GET request" for the resource, with a precondition header that requests that -data be returned only when the version on the service does not match the Etag or last modified date in the header. +data be returned only when the version on the service does not match the ETag or last modified date in the header. If there are no changes, then there is no need to return the resource, as the client already has the most recent version. Implementing this strategy is relatively straightforward. First, you will return an `ETag` with a value that uniquely identifies the instance (or version) of the resource. The [Computing ETags](./Guidelines.md#computing-etags) section provides guidance on how to properly calculate the value of your `ETag`. @@ -517,7 +517,7 @@ This tells the service to compare the `ETag` that came in with the request, with Optimistic concurrency is a strategy used in HTTP to avoid the "lost update" problem that can occur when multiple clients attempt to update a resource simultaneously. Clients can use ETags returned by the service to specify a _precondition_ for the execution of an update, to ensure that the resource has not been updated since the client last observed it. For example, the client can specify an `If-Match` header with the last ETag value received by the client in an update request. -The service processes the update only if the Etag value in the header matches the ETag of the current resource on the server. +The service processes the update only if the ETag value in the header matches the ETag of the current resource on the server. By computing and returning ETags for your resources, you enable clients to avoid using a strategy where the "last write always wins." ## Getting Help: The Azure REST API Stewardship Board From 67e45a5452b9b9f603b37c7f1318c9cfacf053a1 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 30 Jun 2023 14:53:30 -0700 Subject: [PATCH 535/729] Update wording of top level error codes to be more prescriptive. (#463) * Update GuidelinesGraph.md * Update graph/GuidelinesGraph.md Co-authored-by: Darrel --------- Co-authored-by: Darrel --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 02203adb..e0fecabd 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -285,7 +285,7 @@ Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs shoul } ``` -The top-level error code must be aligned with HTTP response status codes according to [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). The following examples demonstrate error modeling for common use cases: +The top-level error code must match the HTTP response status code description, converted to camelCase, as listed in the [Status Code Registry (iana.org)](https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml). The following examples demonstrate error modeling for common use cases: - **Simple error**: An API wants to report an error with top-level details only. The error object contains the top-level error code, message and target (optional). From fd7624b1c659c3dbb6b66a6d40aa1c5194b6a8db Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 24 Jul 2023 08:58:31 -0700 Subject: [PATCH 536/729] Remove the requirement for an enum property in the collection subsets pattern --- graph/patterns/subsets.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/graph/patterns/subsets.md b/graph/patterns/subsets.md index d9dd801c..bdda6e6b 100644 --- a/graph/patterns/subsets.md +++ b/graph/patterns/subsets.md @@ -16,12 +16,18 @@ Existing patterns for this either have special-cased strings or have tightly cou Have an abstract base class where all variants of the subset are derived types from the base subset. For more information, see the [general subtyping guidance](./subtypes.md). -The abstract base class should also hold an enum for all possible variants. The purpose of including this is to allow for easier ways to do query and filter operations on variants like `all` and `none` without relying on `isof` functions. +The abstract base class may also optionally hold an `enum` for the different variants. If it does, the `enum` must have a member for all possible variants. The purpose of including this is to allow for easier ways to do query and filter operations on variants like `all` and `none` without relying on `isof` functions. -**Base type** +**Base type *without* an enum for the variants** ```xml - + +``` + +**Base type *with* an enum for the variants** + +```xml + @@ -36,15 +42,15 @@ The abstract base class should also hold an enum for all possible variants. The **Derived types** ```xml - + - + - + - + ``` @@ -53,7 +59,7 @@ Be aware that the name values and types in the preceding examples are just examp These pattern type names should satisfy the following naming conventions: -- The base type name should have the suffix `Base`, and the enumeration type name should have the suffix `Kind`. +- The base type name should have the suffix `Base`, and the enumeration type name (if an `enum` is defined) should have the suffix `Kind`. - Derived child types should have names with enumeration values as the prefixes; for example, if the enumeration member value is `value1`, then the derived type name is `value1`. ```xml From 47658a5d4ab38595945d03bd1385a6887771b9c2 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 31 Jul 2023 08:14:05 -0700 Subject: [PATCH 537/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e0fecabd..b58e9367 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -168,8 +168,9 @@ Microsoft Graph APIs should support basic query options in conformance with ODat | :heavy_check_mark: **MUST** support `$select on resource` to enable properties projection. | | :ballot_box_with_check: **SHOULD** support `/entityTypeCollection/{id}?$expand=navProp1` option for navigation properties of entities. | | :ballot_box_with_check: **SHOULD** support `$filter` with `eq` and `ne` operations on properties of entity collections. | -| :heavy_check_mark: **MUST** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | -| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip` (or `$skipToken`). | +| :heavy_check_mark: **MUST** support pagination of collections ( of entity types or complex types) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | +| :ballot_box_with_check: **MAY** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using `$skiptoken`. | +| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip`. | | :ballot_box_with_check: **SHOULD** support `$count` for collections. | | :ballot_box_with_check: **SHOULD** support sorting with `$orderby` both ascending and descending on properties of the entities. | From f400863df49397cab122f46160dde8816a9193fc Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 31 Jul 2023 08:14:21 -0700 Subject: [PATCH 538/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index b58e9367..d1bd151c 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -168,7 +168,7 @@ Microsoft Graph APIs should support basic query options in conformance with ODat | :heavy_check_mark: **MUST** support `$select on resource` to enable properties projection. | | :ballot_box_with_check: **SHOULD** support `/entityTypeCollection/{id}?$expand=navProp1` option for navigation properties of entities. | | :ballot_box_with_check: **SHOULD** support `$filter` with `eq` and `ne` operations on properties of entity collections. | -| :heavy_check_mark: **MUST** support pagination of collections ( of entity types or complex types) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | +| :heavy_check_mark: **MUST** support pagination of collections (of entity types or complex types) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | | :ballot_box_with_check: **MAY** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using `$skiptoken`. | | :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip`. | | :ballot_box_with_check: **SHOULD** support `$count` for collections. | From dcf60b68ed36e94b5d47739a511689a2ffcfe645 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 31 Jul 2023 08:21:21 -0700 Subject: [PATCH 539/729] Update Guidelines.md --- Guidelines.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Guidelines.md b/Guidelines.md index 524112c9..3a1742bf 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -954,8 +954,9 @@ Client-driven paging enables clients to request only the number of resources tha Sorting and Filtering parameters MUST be consistent across pages, because both client- and server-side paging is fully compatible with both filtering and sorting. -#### 9.8.1. Server-driven paging -Paginated responses MUST indicate a partial result by including a continuation token in the response. +#### 9.8.1. Continuation tokens + +Paginated responses MUST indicate a partial result by including a continuation token in the response using the OData control information `@nextLink`. The absence of a continuation token means that no additional pages are available. Clients MUST treat the continuation URL as opaque, which means that query options may not be changed while iterating over a set of partial results. @@ -976,7 +977,13 @@ Content-Type: application/json } ``` -#### 9.8.2. Client-driven paging +The `@nextLink` MAY be populated using either server-driven paging or client-driven paging (`@nextLink`s generated using client-driven paging should not include the `$top` query parameter). + +#### 9.8.2. Server-driven paging + +The server MAY provide + +#### 9.8.3. Client-driven paging Clients MAY use _$top_ and _$skip_ query parameters to specify a number of results to return and an offset into the collection. The server SHOULD honor the values specified by the client; however, clients MUST be prepared to handle responses that contain a different page size or contain a continuation token. From f60c48897a4bd7ae17b5d1d2f97319ba5429dcfb Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 31 Jul 2023 08:22:32 -0700 Subject: [PATCH 540/729] Update Guidelines.md --- Guidelines.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Guidelines.md b/Guidelines.md index 3a1742bf..6519d567 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -96,9 +96,10 @@ This document establishes the guidelines Microsoft REST APIs SHOULD follow so RE - [9.7.2. Operator examples](#972-operator-examples) - [9.7.3. Operator precedence](#973-operator-precedence) - [9.8. Pagination](#98-pagination) - - [9.8.1. Server-driven paging](#981-server-driven-paging) - - [9.8.2. Client-driven paging](#982-client-driven-paging) - - [9.8.3. Additional considerations](#983-additional-considerations) + - [9.8.1. Continuation tokens](#981-continuation-tokens) + - [9.8.2. Server-driven paging](#982-server-driven-paging) + - [9.8.3. Client-driven paging](#983-client-driven-paging) + - [9.8.4. Additional considerations](#984-additional-considerations) - [9.9. Compound collection operations](#99-compound-collection-operations) - [9.10. Empty Results](#910-empty-results) - [10. Delta queries](#10-delta-queries) @@ -1008,7 +1009,7 @@ Content-Type: application/json } ``` -#### 9.8.3. Additional considerations +#### 9.8.4. Additional considerations **Stable order prerequisite:** Both forms of paging depend on the collection of items having a stable order. The server MUST supplement any specified order criteria with additional sorts (typically by key) to ensure that items are always ordered consistently. From 86835674da1418f702a6ee324fba0a0070fdb31b Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 31 Jul 2023 08:26:13 -0700 Subject: [PATCH 541/729] Update Guidelines.md --- Guidelines.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index 6519d567..ca273e86 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -982,7 +982,39 @@ The `@nextLink` MAY be populated using either server-driven paging or client-dri #### 9.8.2. Server-driven paging -The server MAY provide +The server MAY provide server-driven paging by populating the continuation token with a `$skiptoken` query parameter. +The `$skiptoken` value is opague for clients and its structure should not be assumed. +`$skiptoken` values SHOULD expire after some period of time. + +Example: + +```http +GET http://api.contoso.com/v1.0/people HTTP/1.1 +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + ..., + "value": [...], + "@nextLink": "http://api.contoso.com/v1.0/people?$skiptoken={opaquetoken}" +} +``` + +```http +GET http://api.contoso.com/v1.0/people?$skiptoken={opaquetoken} HTTP/1.1 +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + ..., + "value": [...], + "@nextLink": "http://api.contoso.com/v1.0/people?$skiptoken={opaquetoken2}" +} +``` #### 9.8.3. Client-driven paging Clients MAY use _$top_ and _$skip_ query parameters to specify a number of results to return and an offset into the collection. From 2069d225f2204b483bb28b34c3d7c585f38bcade Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 31 Jul 2023 08:26:55 -0700 Subject: [PATCH 542/729] Update Guidelines.md --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index ca273e86..d54705b0 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -984,7 +984,7 @@ The `@nextLink` MAY be populated using either server-driven paging or client-dri The server MAY provide server-driven paging by populating the continuation token with a `$skiptoken` query parameter. The `$skiptoken` value is opague for clients and its structure should not be assumed. -`$skiptoken` values SHOULD expire after some period of time. +`$skiptoken` values SHOULD expire after some period of time decided by the server. Example: From d95b26fde7e697fc4abef1e9e58cb6933974f236 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 7 Aug 2023 09:32:05 -0400 Subject: [PATCH 543/729] Create viewpoint.md --- graph/patterns/viewpoint.md | 105 ++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 graph/patterns/viewpoint.md diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md new file mode 100644 index 00000000..d50475f8 --- /dev/null +++ b/graph/patterns/viewpoint.md @@ -0,0 +1,105 @@ +# Pattern name + +Microsoft Graph API Design Pattern + +*Provide a short description of the pattern.* +The viewpoint pattern provides the ability to manage an individual status of a shared object for multiple independent actors. + +## Problem + +Often in an organizational context a group of people receives a common messages but different users act on this messages at a different point of time.So that if user1 read and deleted the message, for user2 this is still unread. usually it happens when a shared item is presented in an individual context. + +## Solution + +*Describe how to implement the solution to solve the problem.* + +Represents user view points data for a serviceUpdateMessage. +Represents user viewpoints data of the service message. This data includes message status such as whether the user has archived, read, or marked the message as favorite. This property is null when accessed with application permissions. +https://learn.microsoft.com/en-us/graph/api/resources/serviceupdatemessage?view=graph-rest-1.0 + +## When to use this pattern + +*Describe when and why the solution is applicable and when it might not be.* + + + + +## Issues and considerations + +*Describe tradeoffs of the solution.* + +## Example +https://microsoftgraph.visualstudio.com/onboarding/_search?action=contents&text=chatViewpoint&type=code&lp=code-Project&filters=ProjectFilters%7Bonboarding%7DRepositoryFilters%7Bonboarding%7D&pageSize=25&result=DefaultCollection/onboarding/onboarding/GBmaster//reviews/15279-ga-chat-lastMessagePreview/api.md + + + + +*Provide a short example from real life.* +``` + + + + + + + + + + + + + + + + + + + + + + + + + +``` + + + +```http +GET https://graph.microsoft.com/v1.0/users/8b081ef6-4792-4def-b2c9-c363a1bf41d5/chats +``` + +```http +HTTP/1.1 200 OK +Content-type: application/json + +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats", + "@odata.count": 3, + "value": [ + { + "id": "19:meeting_MjdhNjM4YzUtYzExZi00OTFkLTkzZTAtNTVlNmZmMDhkNGU2@thread.v2", + "topic": "Meeting chat sample", + "createdDateTime": "2020-12-08T23:53:05.801Z", + "lastUpdatedDateTime": "2020-12-08T23:58:32.511Z", + "chatType": "meeting", + "lastMessagePreview": {...} + , + "viewpoint":{ + "lastMessageReadDateTime": "2021-03-28T21:10:00.000Z" // User has unread messages + } + }, + { + "id": "19:561082c0f3f847a58069deb8eb300807@thread.v2", + "topic": "Group chat sample", + "createdDateTime": "2020-12-03T19:41:07.054Z", + "lastUpdatedDateTime": "2020-12-08T23:53:11.012Z", + "chatType": "group", + "lastMessagePreview": null, // No message was sent in this group chat yet + "viewpoint":{ + "lastMessageReadDateTime": "0000-01-01T00:00:00.000Z" // User hasnt read anything since no message was posted + } + } + ] +} +``` \ No newline at end of file From bca9516819cc47e22fb7c603b33dba30419fc0c0 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 8 Aug 2023 22:40:12 -0400 Subject: [PATCH 544/729] viewpoint with examples --- graph/patterns/viewpoint.md | 93 ++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index d50475f8..40fb08c9 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -1,40 +1,35 @@ -# Pattern name +# Viewpoint Microsoft Graph API Design Pattern -*Provide a short description of the pattern.* -The viewpoint pattern provides the ability to manage an individual status of a shared object for multiple independent actors. -## Problem - -Often in an organizational context a group of people receives a common messages but different users act on this messages at a different point of time.So that if user1 read and deleted the message, for user2 this is still unread. usually it happens when a shared item is presented in an individual context. +*The viewpoint pattern provides the ability to manage an individual status of a shared object for multiple independent actors.* +## Problem +A shared resource, such as a website or a group message, may have different states for different users who access it at different times in an organizational context. For example, user1 may read and delete a message, while user2 may not have seen it yet. This usually happens when a shared item is presented in an individual context. ## Solution -*Describe how to implement the solution to solve the problem.* +The viewpoint pattern provides a solution to how to model an individual user context on a shared resource using a `viewpoint` structural property on an API entity type. +For example, the `viewpoint` property can indicate whether a message is read, deleted, or flagged for a given user. +The consistent naming convention ensures that when a developer uses Graph APIs all ‘viewpoints’ structural properties represent type specific user context across different M365 services and features. -Represents user view points data for a serviceUpdateMessage. -Represents user viewpoints data of the service message. This data includes message status such as whether the user has archived, read, or marked the message as favorite. This property is null when accessed with application permissions. -https://learn.microsoft.com/en-us/graph/api/resources/serviceupdatemessage?view=graph-rest-1.0 - -## When to use this pattern +This pattern simplifies the API client logic by hiding the state transition details and providing state persistency on the server side. The server can manage the different viewpoints for the shared resource without exposing them to the client. You should also make this property filterable. +## Issues and considerations -*Describe when and why the solution is applicable and when it might not be.* - +- Because the `viewpoint` property reflects an individual user's context, it is null when accessed with application permissions. +- Sometimes, the viewpoint can be computed on the server. In this case, an API producer should add OData annotations to the property to provide more information for downstream tools, such as SDKs and documentation generation. +``` + - - -## Issues and considerations + +``` +- An alternative to this design would be to store the user state on the client side. However, this may be problematic in some cases, because of the many devices that a user may have and the need to synchronize the state across them. -*Describe tradeoffs of the solution.* +## Examples -## Example -https://microsoftgraph.visualstudio.com/onboarding/_search?action=contents&text=chatViewpoint&type=code&lp=code-Project&filters=ProjectFilters%7Bonboarding%7DRepositoryFilters%7Bonboarding%7D&pageSize=25&result=DefaultCollection/onboarding/onboarding/GBmaster//reviews/15279-ga-chat-lastMessagePreview/api.md +### Defining a viewpoint - - - -*Provide a short example from real life.* +The following example demonstrates how to define the 'viewpoint' property for the `chat` entity, where a chat is a collection of chatMessages between one or more participants: ``` @@ -44,26 +39,17 @@ https://microsoftgraph.visualstudio.com/onboarding/_search?action=contents&text= - - - + - - - - - - - - - + ... ``` +### Reading an entity with a viewpoint - +The following example shows reading a collection of chats for an identified user, with a viewpoint for each chat: ```http GET https://graph.microsoft.com/v1.0/users/8b081ef6-4792-4def-b2c9-c363a1bf41d5/chats @@ -82,9 +68,7 @@ Content-type: application/json "topic": "Meeting chat sample", "createdDateTime": "2020-12-08T23:53:05.801Z", "lastUpdatedDateTime": "2020-12-08T23:58:32.511Z", - "chatType": "meeting", - "lastMessagePreview": {...} - , + "chatType": "meeting", "viewpoint":{ "lastMessageReadDateTime": "2021-03-28T21:10:00.000Z" // User has unread messages } @@ -94,12 +78,35 @@ Content-type: application/json "topic": "Group chat sample", "createdDateTime": "2020-12-03T19:41:07.054Z", "lastUpdatedDateTime": "2020-12-08T23:53:11.012Z", - "chatType": "group", - "lastMessagePreview": null, // No message was sent in this group chat yet + "chatType": "group", "viewpoint":{ "lastMessageReadDateTime": "0000-01-01T00:00:00.000Z" // User hasnt read anything since no message was posted } } ] } -``` \ No newline at end of file +``` +### Updating a viewpoint + +You can update the `viewpoint` property only if the server does not compute it automatically. Updating the `viewpoint` property usually has a side effect, so you should use an OData action to perform the update. + +The following example shows marking a chat as read for a user: + +```http + +POST https://graph.microsoft.com/beta/chats/19:7d898072-792c-4006-bb10-5ca9f2590649_8ea0e38b-efb3-4757-924a-5f94061cf8c2@unq.gbl.spaces/markChatReadForUser +Content-Type: application/json +Content-length: 106 + +{ + "user": { + "id" : "d864e79f-a516-4d0f-9fee-0eeb4d61fdc2", + "tenantId": "2a690434-97d9-4eed-83a6-f5f13600199a" + } +} +``` +The server responds with a success status code and no payload: + +```http +HTTP/1.1 204 No Content +``` From 482fa428682768cda234b77abc1228aa779dc1a1 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 8 Aug 2023 22:59:33 -0400 Subject: [PATCH 545/729] added viewpoint --- graph/GuidelinesGraph.md | 46 ++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e0fecabd..4de194fd 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -10,9 +10,9 @@ Table of contents - [Design approach](#design-approach) - [Naming](#naming) - [Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) - - [Query support](#query-support) - [Resource modeling patterns](#resource-modeling-patterns) - [Pros and cons](#pros-and-cons) + - [Query support](#query-support) - [Behavior modeling](#behavior-modeling) - [Error handling](#error-handling) - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) @@ -26,6 +26,7 @@ Table of contents | Date | Notes | |-------------|-----------------------------| +| 2023-Aug-8 | New and updated patterns | | 2022-Jun-14 | Edit pass for formatting, links | | 2021-Sep-28 | Using summary and patterns style | | 2020-Oct-04 | Initial version in Wiki | @@ -159,25 +160,6 @@ In Microsoft Graph, a top-level API category might represent one of the followin Effectively, top-level categories define a perimeter for the API surface; thus, a new category creation requires additional rigor and governance approval. -### Query support - -Microsoft Graph APIs should support basic query options in conformance with OData specifications and [Microsoft REST API Guidelines for error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses). - -|Requirements | -|----------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** support `$select on resource` to enable properties projection. | -| :ballot_box_with_check: **SHOULD** support `/entityTypeCollection/{id}?$expand=navProp1` option for navigation properties of entities. | -| :ballot_box_with_check: **SHOULD** support `$filter` with `eq` and `ne` operations on properties of entity collections. | -| :heavy_check_mark: **MUST** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | -| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip` (or `$skipToken`). | -| :ballot_box_with_check: **SHOULD** support `$count` for collections. | -| :ballot_box_with_check: **SHOULD** support sorting with `$orderby` both ascending and descending on properties of the entities. | - -The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or processing the request. One way to avoid this is to use the POST verb instead of GET with the `$query` segment, and pass the query options part of the URL in the request body as described in the chapter -[OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). - -Another way to avoid this is to use JSON batch as described in the [Microsoft Graph batching documentation](https://docs.microsoft.com/graph/json-batching#bypassing-url-length-limitations-with-batching). - ### Resource modeling patterns You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type does not. In Microsoft Graph, this key property is called `id` for server-created key values. If there is a natural name for the key property, then the workload can use that. @@ -248,6 +230,26 @@ Following are a few pros and cons to decide which pattern to use: > **Note:** > As can be seen in a few of the pros and cons, one of the important aspects discussed here is that the API design goes beyond the syntactical aspects of the API. Therefore, it is important to plan ahead how the API evolves, lay the foundation, and allow users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change.** The different modeling patterns differ in how they express syntax and semantics and how they allow the API to evolve without breaking compatibility. For more information, see [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) later in this article. +### Query support + +Microsoft Graph APIs should support basic query options in conformance with OData specifications and [Microsoft REST API Guidelines for error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses). + +|Requirements | +|----------------------------------------------------------------------------------------------------| +| :heavy_check_mark: **MUST** support `$select on resource` to enable properties projection. | +| :ballot_box_with_check: **SHOULD** support `/entityTypeCollection/{id}?$expand=navProp1` option for navigation properties of entities. | +| :ballot_box_with_check: **SHOULD** support `$filter` with `eq` and `ne` operations on properties of entity collections. | +| :heavy_check_mark: **MUST** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | +| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip` (or `$skipToken`). | +| :ballot_box_with_check: **SHOULD** support `$count` for collections. | +| :ballot_box_with_check: **SHOULD** support sorting with `$orderby` both ascending and descending on properties of the entities. | + +The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or processing the request. One way to avoid this is to use the POST verb instead of GET with the `$query` segment, and pass the query options part of the URL in the request body as described in the chapter +[OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). + +Another way to avoid this is to use JSON batch as described in the [Microsoft Graph batching documentation](https://docs.microsoft.com/graph/json-batching#bypassing-url-length-limitations-with-batching). + + ### Behavior modeling The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request/response pattern should be one of the first implementation decisions you make. @@ -267,6 +269,7 @@ Operation resources must have a binding parameter that matches the type of the b For a complete list of standard HTTP operations, see the [Microsoft REST API Guidelines error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses). + ### Error handling Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should follow when returning error condition responses. You can improve API traceability and consistency by using the recommended Microsoft Graph error model and the Microsoft Graph utilities library to provide a standard implementation for your service: @@ -388,7 +391,8 @@ The guidelines in previous sections are intentionally brief and provide a jump s | [Namespace](./patterns/namespace.md) | Organize resource definitions into a logical set. | | [Navigation properties](./patterns/navigation-property.md) | Model resource relationships | | [Operations](./patterns/operations.md) | Model complex business operations | -| [Type hierarchy](./patterns/subtypes.md) | Model `is-a` relationships using subtypes. | +| [Type hierarchy](./patterns/subtypes.md) | Model `is-a` relationships using subtypes. +| [Viewpoint](./patterns/viewpoint.md) | Model user specific properties for a shared resource. ## References From 1f2c06aece79c63cf0008fcdaf296ad4da553852 Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Thu, 3 Aug 2023 16:48:49 -0700 Subject: [PATCH 546/729] Provide a simpler example of a primitive dictionary before dictionary of complex type. Provide separate examples for defining a dictionary, using the dictionary in a type definition, and then querying/adding/updating/removing dictionary entries. --- graph/patterns/dictionary.md | 236 ++++++++++++++++++++--------------- 1 file changed, 135 insertions(+), 101 deletions(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 3f6d2507..9fd4a5fd 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -2,24 +2,24 @@ Microsoft Graph API Design Pattern -*The dictionary type provides the ability to create a set of primitives or objects of the same type where the API consumer can define a name for each value in the set.* +_The dictionary type provides the ability to create a set key/value pairs where the set of keys is dynamically specified by the API consumer._ ## Problem -The API design requires a resource to include an unknown quantity of data elements of the same type that must be named by using values provided by the API consumer. +The API design requires a resource to include an unknown quantity of data values whose keys are defined by the API consumer. ## Solution -API designers use a JSON object to represent a dictionary in an `application/json` response payload. When describing the model in CSDL, a new complex type can be created that derives from `Org.OData.Core.V1.Dictionary` and then uses the `Org.OData.Validation.V1.OpenPropertyTypeConstraint` to constrain the type that can be used for the values in the dictionary as appropriate. +API designers use a JSON object to represent a dictionary in an `application/json` response payload. When describing the model in CSDL, a new complex type can be created that derives from `graph.Dictionary` and optionally uses the `Org.OData.Validation.V1.OpenPropertyTypeConstraint` to constrain the type that can be used for the values in the dictionary as appropriate. -Dictionary values can be added, removed, or modified via `PATCH` to the dictionary property. Values are removed by setting the property to `null`. +Dictionary entries can be added, removed, or modified via `PATCH` to the dictionary property. Entries are removed by setting the property to `null`. ## When to use this pattern Before using a dictionary type in your API definition, make sure that your scenario fits the following criteria: - The data values MUST be related to one another semantically as a collection. -- The value types MUST be a primitive type or a **ComplexType**. Mixed primitive types are not allowed. +- The values MUST be primitive or complex types. - The client MUST define the keys of this type, as opposed to the service defining them in advance. ### Alternatives @@ -31,81 +31,111 @@ Before using a dictionary type in your API definition, make sure that your scena Dictionaries, sometimes called maps, are a collection of name-value pairs. They allow dynamic data sets to be accessed in a systematic manner and are a good compromise between a strictly defined-ahead-of-time structure with all its named properties and a loosely defined dynamic object (such as OData OpenTypes). -Because dictionary entries are removed by setting the value to `null`, dictionaries can only support values that are non-nullable. - -Open questions: - -- Can/should PUT be supported on the dictionary property and/or the entry value? -- Must an implementer support PATCH at both the dictionary level and the entry level? -- Should we also allow DELETE to a property to be equivalent to setting to null? -- Why do we not allow mixed primitives or mixed primitive/complex typed values? what about collections? +Because dictionary entries are removed by setting the value to `null`, dictionaries don't support null values. For more information, see the [OData reference](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md#dictionary). ## Examples -### JSON payload example +### Declaring a string dictionary +The following example demonstrates defining a dictionary that can contain string values. -The following example illustrates the resulting JSON for a property of dictionary type. The parent object has been omitted for brevity. - -```json -{ - "author": { - "domain": "contoso" - }, - "maintainer": { - "domain": "fabrikam" - }, - "architect": { - "domain": "adventureWorks" - } -} +```xml + + + + Edm.String + + + ``` -### HTTP calls examples +### Defining a dictionary property +The following example shows defining a dictionary property, "userTags", on the item entity type. + +```xml + + ... + + +``` -In this set of examples, we model a **roles** property of dictionary type on the user entity, which is exposed by the users entity set. +### Reading a dictionary +Dictionaries are represented in JSON payloads as a JSON object, where the property names are comprised of the keys and their values are the corresponding key values. -#### Get an entry from the dictionary +The following example shows reading an item with a dictionary property named "userTags": ```HTTP -GET https://graph.microsoft.com/v1.0/users/10/roles/author +GET /item ``` - Response: - ```json { - "domain": "contoso" + ... + "userTags": + { + "anniversary": "2002-05-19", + "favoriteMovie": "Princess Bride" + } } ``` -#### Get the dictionary +### Setting a dictionary value +The following example shows setting a dictionary value. If "hairColor" already exists, it is updated, otherwise it is added. -```HTTP -GET https://graph.microsoft.com/v1.0/users/10/roles +```http +PATCH /item/userTags +``` +```json +{ + "hairColor": "purple" +} ``` -Response: - +### Deleting a dictionary value +A dictionary value can be removed by setting the value to null. +```http +PATCH /item/userTags +``` ```json { - "author": { - "domain": "contoso" - }, - "maintainer": { - "domain": "fabrikam" - }, - "architect": { - "domain": "adventureWorks" - } + "hairColor": null } ``` -#### Get the entity with the dictionary +### Declaring a complex typed dictionary +Dictionaries can also contain complex types whose values may be constrained to a particular set of complex types. + +The following example defines a complex type **roleSettings**, an **assignedRoleGroupDictionary** that contains **roleSettings**, and an **assignedRoles** property that uses the dictionary.. + +```xml + + ... + + + + + + + + + + + + microsoft.graph.roleSettings + + + +``` + +### Reading a entity with a complex-typed dictionary + +The following example illustrates reading an entity containing the complex-typed dictionary "assignedRoles". ```HTTP -GET https://graph.microsoft.com/v1.0/users/10 +GET /users/10 ``` Response: @@ -114,7 +144,7 @@ Response: { "id": "10", "displayName": "Jane Smith", - "roles": { + "assignedRoles": { "author": { "domain": "contoso" }, @@ -128,85 +158,89 @@ Response: } ``` -#### Create an entry in the dictionary +### Reading the dictionary property +The following example shows getting just the "assignedRoles" dictionary property. ```HTTP -PATCH https://graph.microsoft.com/v1.0/users/10/roles/author - -{ - "domain": "contoso" -} +GET /users/10/assignedRoles ``` -#### Update the dictionary - -```HTTP -PATCH https://graph.microsoft.com/v1.0/users/10/roles +Response: +```json { "author": { - "domain": "contoso1" + "domain": "contoso" }, "maintainer": { - "domain": "fabrikam1" - }, - "reviewer": { "domain": "fabrikam" }, - "architect": null + "architect": { + "domain": "adventureWorks" + } } ``` -> **Notes:** -> -> - Setting one of the keys to **null** deletes it from the dictionary. -> - The domain values for the existing author and maintainer entries are updated. -> - The reviewer entry is inserted in the dictionary. - -#### Update an entry in the dictionary +#### Reading an individual entry from the dictionary +The following example shows reading a single complex-typed entry named "author" from the "assignedRoles" dictionary. ```HTTP -PATCH https://graph.microsoft.com/v1.0/users/10/roles/author +GET /users/10/assingedRoles/author +``` +Response: + +```json { - "domain": "fabrikam" + "domain": "contoso" } ``` -#### Delete an entry from the dictionary +#### Setting an individual entry in the dictionary +The following examples shows updating the dictionary to set the value for the "author" entry. If the "author" entry does not exists it is added with the specified values; otherwise, if the "author" entry already exists, it is updated with the specified values (unspecified values are left unchanged). ```HTTP -PATCH https://graph.microsoft.com/v1.0/users/10/roles/author +PATCH /users/10/assignedRoles/author +``` +```json { - "domain": null + "author" : { + "domain": "contoso" + } } ``` -### CDSL example +#### Deleting an individual entry from the dictionary +The following example shows deleting the "author" entry by setting its value to null. -The following example defines a complex type **roleSettings** as well as a dictionary of which the key will be a string and the value a **roleSettings**. +```HTTP +PATCH /users/10/assignedRoles +``` +```json +{ + "author": null +} +``` -```xml - - - +#### Setting multiple dictionary entries +The following example sets values for the "author", "maintainer" and "viewer" entries, and removes the "architect" entry by setting it to null. - - - - - microsoft.graph.roleSettings - - - - - GET - PATCH - - - +```HTTP +PATCH /users/10/assignedRoles +``` +```json +{ + "author": { + "domain": "contoso1" + }, + "maintainer": { + "domain": "fabrikam1" + }, + "reviewer": { + "domain": "fabrikam" + }, + "architect": null +} ``` ## See also From 875d657381eb5b02fda59b7117e04903335e5397 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 9 Aug 2023 14:37:34 -0400 Subject: [PATCH 547/729] added antipattern template --- graph/patterns/antiPatternTemplate.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 graph/patterns/antiPatternTemplate.md diff --git a/graph/patterns/antiPatternTemplate.md b/graph/patterns/antiPatternTemplate.md new file mode 100644 index 00000000..c5724539 --- /dev/null +++ b/graph/patterns/antiPatternTemplate.md @@ -0,0 +1,23 @@ + +# Antipattern name + +*name with a negative connotation* + +*Example: Fleabag of properties: + +## Description + +*Example: The flat bag pattern is a known anti-pattern in Microsoft Graph, where multiple variants of a common concept are modeled as a single entity type with all potential properties plus an additional property to distinguish the variants.* + +## Consequences +*Describe the consequences in terms of the developer experience* + +*Example: This is the least recommended modeling choice because it is weakly typed, which increases the number of variations and complexity of solutions, making it difficult to verify the semantic correctness of the API for both clients and producers...* + +## Preferable solutions + +*Example: It is preferable to use type hierarchy and facets patterns.* + +## Example + +*Provide an example of better modeling* From f67729280a81bd6784b4c52347a61c98dfa691d9 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 9 Aug 2023 22:56:00 -0400 Subject: [PATCH 548/729] Update antiPatternTemplate.md --- graph/patterns/antiPatternTemplate.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/graph/patterns/antiPatternTemplate.md b/graph/patterns/antiPatternTemplate.md index c5724539..e73bff87 100644 --- a/graph/patterns/antiPatternTemplate.md +++ b/graph/patterns/antiPatternTemplate.md @@ -3,7 +3,7 @@ *name with a negative connotation* -*Example: Fleabag of properties: +*Example: Fleabag of properties* ## Description @@ -16,7 +16,8 @@ ## Preferable solutions -*Example: It is preferable to use type hierarchy and facets patterns.* +*Example: It is preferable to use type hierarchy and facets patterns. +Should provide a link to a valid pattern or patterns.* ## Example From 5089f6ba63f5a83b4c107ec29c3ee2b0da03dbc2 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Tue, 15 Aug 2023 15:18:41 -0400 Subject: [PATCH 549/729] removed comma --- graph/GuidelinesGraph.md | 2 +- graph/patterns/change-notifications.md | 30 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 graph/patterns/change-notifications.md diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 4de194fd..2aa7e268 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -33,7 +33,7 @@ Table of contents ## Introduction -When building a digital ecosystem API, usability becomes a business priority. The success of your ecosystem depends on APIs that are easy to discover, simple to use, fit for purpose, and consistent across your products. +When building a digital ecosystem API usability becomes a business priority. The success of your ecosystem depends on APIs that are easy to discover, simple to use, fit for purpose, and consistent across your products. This document offers guidance that Microsoft Graph API producer teams MUST follow to ensure that Microsoft Graph has a consistent and easy-to-use API surface. A new API design should meet the following goals: diff --git a/graph/patterns/change-notifications.md b/graph/patterns/change-notifications.md new file mode 100644 index 00000000..26336e73 --- /dev/null +++ b/graph/patterns/change-notifications.md @@ -0,0 +1,30 @@ +# Pattern name + +Microsoft Graph API Design Pattern + +*Provide a short description of the pattern.* + + +## Problem + +*Describe the business context relevant for the pattern.* + +*Provide a short description of the problem.* + +## Solution + +*Describe how to implement the solution to solve the problem.* + +*Describe related patterns.* + +## When to use this pattern + +*Describe when and why the solution is applicable and when it might not be.* + +## Issues and considerations + +*Describe tradeoffs of the solution.* + +## Example + +*Provide a short example from real life.* From 55ffb8922e4bbb3df139cc3291450b0fc661711e Mon Sep 17 00:00:00 2001 From: "Johan Stenberg (MSFT)" Date: Tue, 15 Aug 2023 17:26:32 -0700 Subject: [PATCH 550/729] Make guidance for polymorphism use more permissive. (#484) --- azure/ConsiderationsForServiceDesign.md | 7 +------ azure/Guidelines.md | 5 +++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 15d5e15b..0b37c2f4 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -180,12 +180,7 @@ This header is targeted at developers or operation professionals, and it is inte A major inhibitor to adoption and usage is when an API behaves in an unexpected way. Often, these are subtle design decisions that seem benign at the time, but end up introducing significant downstream friction for developers. One common area of friction for developers is _polymorphism_ -- where a value may have any of several types or structures. -Polymorphism can be beneficial in certain cases, e.g. as a way to express inheritance, but also creates friction -because it requires the value to be introspected before being processed and cannot be represented in a natural/useful way in many type-safe languages. - -:ballot_box_with_check: **YOU SHOULD** avoid polymorphism, especially in the response. An endpoint **YOU SHOULD** work with a single type to avoid problems during SDK creation. - -:ballot_box_with_check: **YOU SHOULD** return a homogeneous collection (single type). Do not return heterogeneous collections unless there is a really good reason to do so. If you feel heterogeneous collections are required, discuss the requirement with an API reviewer prior to implementation. +Polymorphism can be beneficial in certain cases, e.g. as a way to express inheritance, but also creates friction because it requires the value to be introspected before being processed and cannot be represented in a natural/useful way in many nominally typed languages. The use of a discriminator field (`kind`) simplifies the introspection, but developers frequently end up having to explicitly cast the response to the appropriate type in order to use it. Collections are another common area of friction for developers. It is important to define collections in a consistent manner within your service and across services of the platform. In particular, features such as pagination, filtering, and sorting, when supported, should follow common API patterns. See [Collections](./Guidelines.md#collections) for specific guidance. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 2efaf0d0..ae15b2b3 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -430,13 +430,14 @@ This indicates to client libraries and customers that values of the enumeration #### Polymorphic types -:warning: **YOU SHOULD NOT** use polymorphic JSON types because they greatly complicate the customer code due to runtime dynamic casts and the introduction of new types in the future. +Polymorphism types in REST APIs refers to the possibility to use the same property of a request or response to have similar but different shapes. This is commonly expressed as a `oneOf` in JsonSchema or OpenAPI. In order to simplify how to determine which specific type a given request or response payload corresponds to, Azure requires the use of an explicit discriminator field. -If you can't avoid them, then follow the guideline below. +Note: Polymorphic types can make your service more difficult for nominally typed languages to consume. See the corresponding section in the [Considerations for service design](./ConsiderationsForServiceDesign.md#avoid-surprises) for more information. :white_check_mark: **DO** define a discriminator field indicating the kind of the resource and include any kind-specific fields in the body. Below is an example of JSON for a Rectangle and Circle with a discriminator field named `kind`: + **Rectangle** ```json { From 2f80abb2c043df96d275af3140a44939f94279d7 Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Thu, 17 Aug 2023 22:03:44 +0100 Subject: [PATCH 551/729] First draft for upsert pattern --- graph/patterns/idempotent-operations.md | 172 ++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 graph/patterns/idempotent-operations.md diff --git a/graph/patterns/idempotent-operations.md b/graph/patterns/idempotent-operations.md new file mode 100644 index 00000000..a15c1a13 --- /dev/null +++ b/graph/patterns/idempotent-operations.md @@ -0,0 +1,172 @@ +# Infrastructure as code + +Microsoft Graph API Design Pattern + +*Infrastructure as code (IaC) resource pattern ensures that repeated operations to a resource always results in a predictable resource end-state.* + +## Problem + +Infrastructure as code (IaC) defines system resources and topologies in a descriptive manner that allows teams to manage those resources as they would code. +Practicing IaC helps teams deploy system resources in a reliable, repeatable, and controlled way. +IaC also helps automate deployment and reduces the risk of human error, especially for complex large environments. +Customers want to adopt IaC practices for many of the resources managed through Microsoft Graph. + +Most resources' creation operations in Microsoft Graph are not idempotent in nature. +As a consequence, API consumers that want to offer IaC solutions, must create compensation layers that can mimic idempotent behavior. +For example, when creating a resource, the compensation layer must check whether the resource first exists, before trying to create or update the resource. + +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. + +## Solution + +The solution is to use an `UPSERT` pattern, to solve for the non-idempotent creation and client-provided naming problems. + +* For IaC scenarios, resources must use `UPSERT` semantics with an [alternate key](./alternate-key.md): + * Use `PATCH` with a client-provided alternate key. + * 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. + * For an existing resource (specified by the alternate key) the service must handle this as an "update. + * Any new alternate key, used for IaC scenarios, be called `uniqueName`. + * NOTE: the service must also support `GET` using the alternate key pattern. +* 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. +* If a service does not support `UPSERT`, then a `PATCH` call against a non-existent resource must result in an HTTP "409 conflict" error. + +This solution allows for existing resources that follow Microsoft Graph conventions for CRUD operations to add `UPSERT` without impacting existing apps or functionality. + +Ideally, all new entity types should support an `UPSERT` mechanism, especially if the resource is likely be used in IaC scenarios. + +## When to use this pattern + +This pattern should be adopted for resources that are managed through infrastructure as code or desired state configuration. + +## Issues and considerations + +* The addition of this new pattern (with alternate key) does not represent a breaking change. +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. +As a result, API producers can use the `Prefer: idempotent` to require clients to opt-in to the UPSERT behavior. +* 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. +* 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. +* 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. + +```xml + + + + + + + +``` + +## Examples + +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`). + +```xml + + + + + + + + + + + + + + + + + + + + + + + +``` + +### Upserting a record (creation path) + +Create a new group, with a `uniqueName` of "Group157". In this case, this group does not exist. + +```http +PATCH /groups(uniqueName='Group157') +``` + +```json +{ + "displayName": "My favorite group", + "description": "All my favorite people in the world" +} +``` + +Response: + +```http +201 created +``` + +```json +{ + "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66", + "displayName": "My favorite group", + "description": "All my favorite people in the world", + "uniqueName": "Group157" +} +``` + +### Upserting a record (update path) + +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. + +```http +PATCH /groups(uniqueName='Group157') +``` + +```json +{ + "displayName": "My favorite group", + "description": "All my favorite people in the world" +} +``` + +Response: + +```http +200 ok +``` + +```json +{ + "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66", + "displayName": "My favorite group", + "description": "All my favorite people in the world", + "uniqueName": "Group157" +} +``` + +Notice how this operation is idempotent in nature, rather than returning a 409 conflict error. + +### Upsert not supported + +Create a new group, with a `uniqueName` of "Group157". In this case, this group does not exist and additionally +the service does not `UPSERT` for groups. + +```http +PATCH /groups(uniqueName='Group157') +``` + +```json +{ + "displayName": "My favorite group", + "description": "All my favorite people in the world" +} +``` + +Response: + +```http +409 conflict +``` From 77fb22e8d0c521b10b136af305bcf509ff6b0a31 Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Thu, 17 Aug 2023 22:05:24 +0100 Subject: [PATCH 552/729] First draft of upsert pattern --- .../{idempotent-operations.md => infrastructure-as-code.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename graph/patterns/{idempotent-operations.md => infrastructure-as-code.md} (97%) diff --git a/graph/patterns/idempotent-operations.md b/graph/patterns/infrastructure-as-code.md similarity index 97% rename from graph/patterns/idempotent-operations.md rename to graph/patterns/infrastructure-as-code.md index a15c1a13..1df7aeb8 100644 --- a/graph/patterns/idempotent-operations.md +++ b/graph/patterns/infrastructure-as-code.md @@ -2,7 +2,7 @@ Microsoft Graph API Design Pattern -*Infrastructure as code (IaC) resource pattern ensures that repeated operations to a resource always results in a predictable resource end-state.* +*Infrastructure as code (IaC) resource pattern ensures that system resources can be deployed in a reliable, repeatable, and controlled way, normally via idempotent operations.* ## Problem From c7c83ae6388070afecc20e0a5d5a884244e04368 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 18 Aug 2023 09:52:40 -0700 Subject: [PATCH 553/729] Update long-running-operations.md --- graph/patterns/long-running-operations.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/graph/patterns/long-running-operations.md b/graph/patterns/long-running-operations.md index 1492e938..c932011e 100644 --- a/graph/patterns/long-running-operations.md +++ b/graph/patterns/long-running-operations.md @@ -57,6 +57,11 @@ There are some deviations from the base guidelines where Microsoft Graph API sta - The API response says the operation resource is being created at the URL provided in the Location header and indicates that the request is not completed by including a 202 status code. - Microsoft Graph doesn’t allow tenant-wide operation resources; therefore, stepwise operations are often modeled as a navigation property on the target resource. +- For most implementations of the LRO pattern, there will be 3 permissions necessary to comply with the principle of least privilege: `{operation}.ReadWrite.All` to create the operation entity, `{operation.Read.All}` to track the operation entity to completion, and `{resource}.Read.All` to retrieve the resource that was created as a result of the operation. +For APIs that would have been modeled as a simple `GET` on the resource URL, but that are modeled as long-running operations due to MSGraph performance requirements, only the `{resource}.Read.All` permission is necessary as long as creating the operation entity is "safe". +Here, "safe" means that there are no side effects for creating the operation entity that would change the functioning of any entities outside of the resource being retrieved. +This requirment is less strict than idempotence, and an idempotent API is suffucient to meet this requirement. + ## When to use this pattern Any API call that is expected to take longer than one second in the 99th percentile should use the long running operations pattern. @@ -254,4 +259,4 @@ HTTP/1.1 202 Accepted Location: https://graph.microsoft.com/v1.0/storage/operations/123 -``` \ No newline at end of file +``` From 9ff0e15825f28b67ef6318232af292319ff95949 Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Fri, 18 Aug 2023 18:00:52 +0100 Subject: [PATCH 554/729] Addressed most of eketo-msft commments --- .../patterns/{infrastructure-as-code.md => upsert.md} | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) rename graph/patterns/{infrastructure-as-code.md => upsert.md} (90%) diff --git a/graph/patterns/infrastructure-as-code.md b/graph/patterns/upsert.md similarity index 90% rename from graph/patterns/infrastructure-as-code.md rename to graph/patterns/upsert.md index 1df7aeb8..b183154d 100644 --- a/graph/patterns/infrastructure-as-code.md +++ b/graph/patterns/upsert.md @@ -1,12 +1,12 @@ -# Infrastructure as code +# Upsert Microsoft Graph API Design Pattern -*Infrastructure as code (IaC) resource pattern ensures that system resources can be deployed in a reliable, repeatable, and controlled way, normally via idempotent operations.* +*The `UPSERT` pattern is a non-destructive idempotent operation using a client-provided key, that ensures that system resources can be deployed in a reliable, repeatable, and controlled way, typically used in Infrastructure as Code (IaC) scenarios.* ## Problem -Infrastructure as code (IaC) defines system resources and topologies in a descriptive manner that allows teams to manage those resources as they would code. +Infrastructure as code (IaC) defines system resources and topologies in a descriptive manner that allows teams to manage those resources as they would code. Practicing IaC helps teams deploy system resources in a reliable, repeatable, and controlled way. IaC also helps automate deployment and reduces the risk of human error, especially for complex large environments. Customers want to adopt IaC practices for many of the resources managed through Microsoft Graph. @@ -25,8 +25,8 @@ The solution is to use an `UPSERT` pattern, to solve for the non-idempotent crea * Use `PATCH` with a client-provided alternate key. * 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. * For an existing resource (specified by the alternate key) the service must handle this as an "update. - * Any new alternate key, used for IaC scenarios, be called `uniqueName`. - * NOTE: the service must also support `GET` using the alternate key pattern. + * Any new alternate key, used for IaC scenarios, should be called `uniqueName`, if there isn't already an existing property that could be used as an alternate key. +* NOTE: the service must also support `GET` using the alternate key pattern. * 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. * If a service does not support `UPSERT`, then a `PATCH` call against a non-existent resource must result in an HTTP "409 conflict" error. @@ -43,6 +43,7 @@ This pattern should be adopted for resources that are managed through infrastruc * The addition of this new pattern (with alternate key) does not represent a breaking change. 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. As a result, API producers can use the `Prefer: idempotent` to require clients to opt-in to the UPSERT behavior. +* 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. * 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. * 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. * 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. From fa4e3a762833bb3516d12fabb3733b5f57294968 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:42:38 -0700 Subject: [PATCH 555/729] Update long-running-operations.md --- graph/patterns/long-running-operations.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph/patterns/long-running-operations.md b/graph/patterns/long-running-operations.md index c932011e..a340c4d5 100644 --- a/graph/patterns/long-running-operations.md +++ b/graph/patterns/long-running-operations.md @@ -57,10 +57,10 @@ There are some deviations from the base guidelines where Microsoft Graph API sta - The API response says the operation resource is being created at the URL provided in the Location header and indicates that the request is not completed by including a 202 status code. - Microsoft Graph doesn’t allow tenant-wide operation resources; therefore, stepwise operations are often modeled as a navigation property on the target resource. -- For most implementations of the LRO pattern, there will be 3 permissions necessary to comply with the principle of least privilege: `{operation}.ReadWrite.All` to create the operation entity, `{operation.Read.All}` to track the operation entity to completion, and `{resource}.Read.All` to retrieve the resource that was created as a result of the operation. -For APIs that would have been modeled as a simple `GET` on the resource URL, but that are modeled as long-running operations due to MSGraph performance requirements, only the `{resource}.Read.All` permission is necessary as long as creating the operation entity is "safe". -Here, "safe" means that there are no side effects for creating the operation entity that would change the functioning of any entities outside of the resource being retrieved. -This requirment is less strict than idempotence, and an idempotent API is suffucient to meet this requirement. +- For most implementations of the LRO pattern (like the example above), there will be 3 permissions necessary to comply with the principle of least privilege: `ArchiveOperation.ReadWrite.All` to create the operation entity, `ArchiveOperation.Read.All` to track the operation entity to completion, and `Archives.Read.All` to retrieve the resource that was created as a result of the operation. +For APIs that would have been modeled as a simple `GET` on the resource URL, but that are modeled as long-running operations due to MSGraph performance requirements, only the `Archive.Read.All` permission is necessary as long as creating the `archiveOperation` entity is "safe". +Here, "safe" means that there are no side effects for creating the `archiveOperation` entity that would change the functionality of any entities outside of the `archive` being retrieved. +This requirment does not require the API to be idempotent, but an idempotent API is suffucient to meet this requirement. ## When to use this pattern From 7ab52f10c80b7da6f7577e420b57078c2a5b2bd7 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:47:36 -0700 Subject: [PATCH 556/729] Update long-running-operations.md --- graph/patterns/long-running-operations.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/long-running-operations.md b/graph/patterns/long-running-operations.md index a340c4d5..c03ca13b 100644 --- a/graph/patterns/long-running-operations.md +++ b/graph/patterns/long-running-operations.md @@ -57,10 +57,10 @@ There are some deviations from the base guidelines where Microsoft Graph API sta - The API response says the operation resource is being created at the URL provided in the Location header and indicates that the request is not completed by including a 202 status code. - Microsoft Graph doesn’t allow tenant-wide operation resources; therefore, stepwise operations are often modeled as a navigation property on the target resource. -- For most implementations of the LRO pattern (like the example above), there will be 3 permissions necessary to comply with the principle of least privilege: `ArchiveOperation.ReadWrite.All` to create the operation entity, `ArchiveOperation.Read.All` to track the operation entity to completion, and `Archives.Read.All` to retrieve the resource that was created as a result of the operation. +- For most implementations of the LRO pattern (like the example above), there will be 3 permissions necessary to comply with the principle of least privilege: `ArchiveOperation.ReadWrite.All` to create the `archiveOperation` entity, `ArchiveOperation.Read.All` to track the `archiveOperation` entity to completion, and `Archives.Read.All` to retrieve the `archive` that was created as a result of the operation. For APIs that would have been modeled as a simple `GET` on the resource URL, but that are modeled as long-running operations due to MSGraph performance requirements, only the `Archive.Read.All` permission is necessary as long as creating the `archiveOperation` entity is "safe". -Here, "safe" means that there are no side effects for creating the `archiveOperation` entity that would change the functionality of any entities outside of the `archive` being retrieved. -This requirment does not require the API to be idempotent, but an idempotent API is suffucient to meet this requirement. +Here, "safe" means that there are no side effects of creating the `archiveOperation` entity that would change the functionality of any entities outside of the `archive` being retrieved. +This requirment does not mean that the API must be idempotent, but an idempotent API is suffucient to meet this requirement. ## When to use this pattern From 3fcd0f3fb020f5d96b021fa236926c8af79b4c44 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:48:15 -0700 Subject: [PATCH 557/729] Update long-running-operations.md --- graph/patterns/long-running-operations.md | 48 +++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/graph/patterns/long-running-operations.md b/graph/patterns/long-running-operations.md index c03ca13b..cd4a8905 100644 --- a/graph/patterns/long-running-operations.md +++ b/graph/patterns/long-running-operations.md @@ -108,7 +108,7 @@ A client wants to provision a new database: POST https://graph.microsoft.com/v1.0/storage/databases/ { -"displayName": "Retail DB", + "displayName": "Retail DB", } ``` @@ -121,10 +121,10 @@ HTTP/1.1 201 Created Location: https://graph.microsoft.com/v1.0/storage/databases/db1 { -"id": "db1", -"displayName": "Retail DB", -"status": "provisioning", -[ … other fields for "database" …] + "id": "db1", + "displayName": "Retail DB", + "status": "provisioning", + [ … other fields for "database" …] } ``` @@ -135,10 +135,10 @@ GET https://graph.microsoft.com/v1.0/storage/databases/db1 HTTP/1.1 200 Ok { -"id": "db1", -"displayName": "Retail DB", -"status": "succeeded", -[ … other fields for "database" …] + "id": "db1", + "displayName": "Retail DB", + "status": "succeeded", + [ … other fields for "database" …] } ``` @@ -161,10 +161,10 @@ HTTP/1.1 202 Accepted Retry-After: 30 { -"id": "db1", -"displayName": "Retail DB", -"status": "deleting", -[ … other fields for "database" …] + "id": "db1", + "displayName": "Retail DB", + "status": "deleting", + [ … other fields for "database" …] } ``` @@ -181,8 +181,8 @@ HTTP/1.1 404 Not Found POST https://graph.microsoft.com/v1.0/storage/archives/ { -"displayName": "Image Archive", -... + "displayName": "Image Archive", + ... } ``` @@ -211,9 +211,9 @@ HTTP/1.1 200 OK Retry-After: 30 { -"createdDateTime": "2015-06-19T12-01-03.4Z", -"lastActionDateTime": "2015-06-19T12-01-03.45Z", -"status": "running" + "createdDateTime": "2015-06-19T12-01-03.4Z", + "lastActionDateTime": "2015-06-19T12-01-03.45Z", + "status": "running" } ``` @@ -232,10 +232,10 @@ location: HTTP/1.1 200 OK { -"createdDateTime": "2015-06-19T12-01-03.45Z", -"lastActionDateTime": "2015-06-19T12-06-03.0024Z", -"status": "succeeded", -"resourceLocation": "https://graph.microsoft.com/v1.0/storage/archives/987" + "createdDateTime": "2015-06-19T12-01-03.45Z", + "lastActionDateTime": "2015-06-19T12-06-03.0024Z", + "status": "succeeded", + "resourceLocation": "https://graph.microsoft.com/v1.0/storage/archives/987" } ``` @@ -245,8 +245,8 @@ HTTP/1.1 200 OK POST https://graph.microsoft.com/v1.0/storage/copyArchive { -"displayName": "Image Archive", -"destination": "Second-tier storage" + "displayName": "Image Archive", + "destination": "Second-tier storage" ... } ``` From 3afb8d1c6055dbad44e87345a9673d08f313dd84 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Sun, 20 Aug 2023 17:39:59 -0400 Subject: [PATCH 558/729] removed change notifications --- graph/patterns/change-notifications.md | 30 -------------------------- 1 file changed, 30 deletions(-) delete mode 100644 graph/patterns/change-notifications.md diff --git a/graph/patterns/change-notifications.md b/graph/patterns/change-notifications.md deleted file mode 100644 index 26336e73..00000000 --- a/graph/patterns/change-notifications.md +++ /dev/null @@ -1,30 +0,0 @@ -# Pattern name - -Microsoft Graph API Design Pattern - -*Provide a short description of the pattern.* - - -## Problem - -*Describe the business context relevant for the pattern.* - -*Provide a short description of the problem.* - -## Solution - -*Describe how to implement the solution to solve the problem.* - -*Describe related patterns.* - -## When to use this pattern - -*Describe when and why the solution is applicable and when it might not be.* - -## Issues and considerations - -*Describe tradeoffs of the solution.* - -## Example - -*Provide a short example from real life.* From 729c96bff35f73101b896030d634a9f73c6a0d86 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:46:28 -0400 Subject: [PATCH 559/729] Update graph/patterns/viewpoint.md Co-authored-by: Dan Kershaw [MSFT] --- graph/patterns/viewpoint.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index 40fb08c9..4c04d952 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -13,7 +13,7 @@ The viewpoint pattern provides a solution to how to model an individual user con For example, the `viewpoint` property can indicate whether a message is read, deleted, or flagged for a given user. The consistent naming convention ensures that when a developer uses Graph APIs all ‘viewpoints’ structural properties represent type specific user context across different M365 services and features. -This pattern simplifies the API client logic by hiding the state transition details and providing state persistency on the server side. The server can manage the different viewpoints for the shared resource without exposing them to the client. You should also make this property filterable. +This pattern simplifies the API client logic by hiding the state transition details and providing state persistency on the server side. The server can manage the different viewpoints for the shared resource without exposing them to the client. The `viewpoint` property should also be filterable. ## Issues and considerations - Because the `viewpoint` property reflects an individual user's context, it is null when accessed with application permissions. From 63d72018878b4ae5abcdd1853a5b89aadbbca49f Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:48:58 -0400 Subject: [PATCH 560/729] removed ags attributes --- graph/patterns/viewpoint.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index 4c04d952..92969069 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -20,7 +20,7 @@ This pattern simplifies the API client logic by hiding the state transition deta - Sometimes, the viewpoint can be computed on the server. In this case, an API producer should add OData annotations to the property to provide more information for downstream tools, such as SDKs and documentation generation. ``` - + ``` - An alternative to this design would be to store the user state on the client side. However, this may be problematic in some cases, because of the many devices that a user may have and the need to synchronize the state across them. @@ -31,12 +31,12 @@ This pattern simplifies the API client logic by hiding the state transition deta The following example demonstrates how to define the 'viewpoint' property for the `chat` entity, where a chat is a collection of chatMessages between one or more participants: ``` - + - + From e93e55b5961a296ee40332c45b65bf1a51127fb8 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 21 Aug 2023 17:28:47 -0700 Subject: [PATCH 561/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e0fecabd..a5722d0f 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -125,6 +125,7 @@ Following is a short summary of the most often used conventions. | :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem or userId on auditActor
    - **Wrong:** webUrl on contact when it's the companyWebUrl | | :ballot_box_with_check: **SHOULD** prefix Boolean properties with `is`, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong:** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom (subjectively more natural than the following examples)
    - **Wrong:** isNewTimeProposalsAllowed or isInvitesFromAllowed (subjectively more awkward that the preceding examples) | | :no_entry: **MUST NOT** use collection, response, or request suffixes. | - **Right:** addresses
    - **Wrong:** addressCollection | +| :no_entry: **MUST NOT** contain product names. | - **Right:** chatMessages
    - **Wrong:** teamsMessages | ### Uniform Resource Locators (URLs) From e943d389490809af2b5aaf45303b8c1a5ea06f35 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 23 Aug 2023 12:50:24 -0400 Subject: [PATCH 562/729] soften recommendation --- graph/patterns/viewpoint.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index 92969069..9cbd4b56 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -10,10 +10,10 @@ A shared resource, such as a website or a group message, may have different stat ## Solution The viewpoint pattern provides a solution to how to model an individual user context on a shared resource using a `viewpoint` structural property on an API entity type. -For example, the `viewpoint` property can indicate whether a message is read, deleted, or flagged for a given user. +For example, the `viewpoint` property can indicate whether a message is read, deleted, or flagged for a given user. The consistent naming convention ensures that when a developer uses Graph APIs all ‘viewpoints’ structural properties represent type specific user context across different M365 services and features. -This pattern simplifies the API client logic by hiding the state transition details and providing state persistency on the server side. The server can manage the different viewpoints for the shared resource without exposing them to the client. The `viewpoint` property should also be filterable. +This pattern simplifies the API client logic by hiding the state transition details and providing state persistency on the server side. The server can manage the different viewpoints for the shared resource without exposing additional complexity to the client.To support queries for a user state the `viewpoint` property should support be filterable. ## Issues and considerations - Because the `viewpoint` property reflects an individual user's context, it is null when accessed with application permissions. @@ -88,7 +88,7 @@ Content-type: application/json ``` ### Updating a viewpoint -You can update the `viewpoint` property only if the server does not compute it automatically. Updating the `viewpoint` property usually has a side effect, so you should use an OData action to perform the update. +You can update the `viewpoint` property only if the server does not compute it automatically. Updating the `viewpoint` property usually has a side effect, so you may want to use an OData action to perform the update. The following example shows marking a chat as read for a user: From d2435ecfd49ad05bc87ea02774c69d4ae629ddf1 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Wed, 23 Aug 2023 10:55:22 -0600 Subject: [PATCH 563/729] add enum tip --- graph/GuidelinesGraph.md | 73 +++++++++++++++++++++++++++++++ graph/patterns/evolvable-enums.md | 2 + 2 files changed, 75 insertions(+) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e0fecabd..bddd4c0b 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -15,6 +15,7 @@ Table of contents - [Pros and cons](#pros-and-cons) - [Behavior modeling](#behavior-modeling) - [Error handling](#error-handling) + - [Enums](#enums) - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) - [Versioning and deprecation](#versioning-and-deprecation) - [Recommended API design patterns](#recommended-api-design-patterns) @@ -326,6 +327,78 @@ For a complete mapping of error codes to HTTP statuses, see +### Enums + +In OData, enum are nominal types representing a subset of the nominal type they use, and are especially useful in cases where certain properties have predefined, limited options. + +```xml + + + + + +``` + +#### Pros + +- Our SDK generators will translate the enum to the best representation of the target programming language, resulting in a better developer experience and free client side validation + +#### Cons + +- Adding a new value requires to go through a (generally fast) API Review +- If the enum is not [evolvable](./patterns/evolvable-enums.md), adding a new value is a breaking change and will generally not be allowed + +#### Enum or Booleans + +Enumerations are a good alternative to booleans when one of the two values (`true`, `false`) conveyes other possible values not yet conceived. Let's assume we have an `Error` type and a property to communicate how to display it: + +```xml + + + + + +``` + +The `false` value here merely communicates that the error shall not be displayed as a tip. What if, in the future, the error could be displayed as a `tip` or `alert`, and then in a more distant future, a `dialog` option is viable? + +With the current model, the only way is to add more boolean properties to convey the new information: + +```diff + + + + ++ ++ + +``` + +Additionally speaking, the workload will now also have to validate the data structure and make sure that only one of the 3 values is `true` + +By using an evolvable enum, instead, all we need to do is to add new members: + +```diff + + + ++ +- +- +- + +``` + +```xml + + + + + + +``` + + ## API contract and non-backward compatible changes The Microsoft Graph definition of breaking changes is based on the diff --git a/graph/patterns/evolvable-enums.md b/graph/patterns/evolvable-enums.md index 0039f300..6d2defbf 100644 --- a/graph/patterns/evolvable-enums.md +++ b/graph/patterns/evolvable-enums.md @@ -4,6 +4,8 @@ Microsoft Graph API Design Pattern *The evolvable enums pattern allows API producers to extend enumerated types with new members without breaking API consumers.* +Note: You might be interested in reading the [Enum guidance](../GuidelinesGraph.md#enums) first + ## Problem Frequently API producers want to add new members to an enum type after it is initially published. Some serialization libraries might fail when they encounter members in an enum type that were added after the serialization model was generated. In this documentation, we refer to any added enum members as unknown. From 9fa84af364bc02ae3ec31769b3604ccbd6c1b8a0 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:00:50 -0700 Subject: [PATCH 564/729] Update subsets.md --- graph/patterns/subsets.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/graph/patterns/subsets.md b/graph/patterns/subsets.md index bdda6e6b..c4f0a602 100644 --- a/graph/patterns/subsets.md +++ b/graph/patterns/subsets.md @@ -182,3 +182,36 @@ _Note: Unrelated properties on entities are omitted for easier readability._ } } ``` + +### Filter when base type has the "kind" enum property + +```HTTP +GET .../memberships?$filter=membershipKind eq 'all' + +200 OK +{ + "value": [ + { + "@odata.type": "#microsoft.graph.allMembership", + "membershipKind": "all" + }, + ... + ] +} +``` + +### Filter when base type lacks the "kind" enum property + +```HTTP +GET .../memberships?$filter=isof(microsoft.graph.allMembership) + +200 OK +{ + "value": [ + { + "@odata.type": "#microsoft.graph.allMembership", + }, + ... + ] +} +``` From 72a6f948e3312e00c94e1509cad2f6189674549a Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 23 Aug 2023 18:27:01 -0400 Subject: [PATCH 565/729] Update graph/patterns/antiPatternTemplate.md Co-authored-by: Dan Kershaw [MSFT] --- graph/patterns/antiPatternTemplate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/antiPatternTemplate.md b/graph/patterns/antiPatternTemplate.md index e73bff87..dff45fd9 100644 --- a/graph/patterns/antiPatternTemplate.md +++ b/graph/patterns/antiPatternTemplate.md @@ -3,7 +3,7 @@ *name with a negative connotation* -*Example: Fleabag of properties* +*Example: Flatbag of properties* ## Description From f813654eb2d41a62c06cd880803249b93454a8e5 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 23 Aug 2023 18:46:44 -0400 Subject: [PATCH 566/729] Update graph/patterns/viewpoint.md Co-authored-by: Darrel --- graph/patterns/viewpoint.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index 9cbd4b56..ea52b818 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -3,7 +3,7 @@ Microsoft Graph API Design Pattern -*The viewpoint pattern provides the ability to manage an individual status of a shared object for multiple independent actors.* +*The viewpoint pattern provides the ability to manage properties of a shared object that have different values for different users.* ## Problem A shared resource, such as a website or a group message, may have different states for different users who access it at different times in an organizational context. For example, user1 may read and delete a message, while user2 may not have seen it yet. This usually happens when a shared item is presented in an individual context. From 5678165e52b95cb837917229d4dfa8b03ed81a67 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 23 Aug 2023 18:47:05 -0400 Subject: [PATCH 567/729] Update graph/patterns/viewpoint.md Co-authored-by: Darrel --- graph/patterns/viewpoint.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index ea52b818..33775e33 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -13,7 +13,7 @@ The viewpoint pattern provides a solution to how to model an individual user con For example, the `viewpoint` property can indicate whether a message is read, deleted, or flagged for a given user. The consistent naming convention ensures that when a developer uses Graph APIs all ‘viewpoints’ structural properties represent type specific user context across different M365 services and features. -This pattern simplifies the API client logic by hiding the state transition details and providing state persistency on the server side. The server can manage the different viewpoints for the shared resource without exposing additional complexity to the client.To support queries for a user state the `viewpoint` property should support be filterable. +This pattern simplifies the API client logic by hiding the state transition details and providing state persistency on the server side. The server can manage the different viewpoints for the shared resource without exposing additional complexity to the client.To support queries for a user state the `viewpoint` property should support filtering. ## Issues and considerations - Because the `viewpoint` property reflects an individual user's context, it is null when accessed with application permissions. From ed234087ce58ec20abe23544b749e3209c890c6f Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 24 Aug 2023 12:20:18 -0400 Subject: [PATCH 568/729] added PATCH example --- graph/patterns/viewpoint.md | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index 33775e33..84927dff 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -24,6 +24,7 @@ This pattern simplifies the API client logic by hiding the state transition deta ``` - An alternative to this design would be to store the user state on the client side. However, this may be problematic in some cases, because of the many devices that a user may have and the need to synchronize the state across them. +- Often, updating the `viewpoint` property may cause a side effect, so you might consider an OData action to do the update. For some user scenarios, the `PATCH` method could be a better way to update a `viewpoint`. ## Examples @@ -70,7 +71,8 @@ Content-type: application/json "lastUpdatedDateTime": "2020-12-08T23:58:32.511Z", "chatType": "meeting", "viewpoint":{ - "lastMessageReadDateTime": "2021-03-28T21:10:00.000Z" // User has unread messages + "lastMessageReadDateTime": "2021-03-28T21:10:00.000Z" + // User has unread messages } }, { @@ -80,17 +82,16 @@ Content-type: application/json "lastUpdatedDateTime": "2020-12-08T23:53:11.012Z", "chatType": "group", "viewpoint":{ - "lastMessageReadDateTime": "0000-01-01T00:00:00.000Z" // User hasnt read anything since no message was posted + "lastMessageReadDateTime": "0000-01-01T00:00:00.000Z" + // User hasnt read anything since no message was posted } } ] } ``` -### Updating a viewpoint +### Updating a viewpoint using an action -You can update the `viewpoint` property only if the server does not compute it automatically. Updating the `viewpoint` property usually has a side effect, so you may want to use an OData action to perform the update. - -The following example shows marking a chat as read for a user: +The following example shows marking a chat `viewpoint` as read for a user using an action: ```http @@ -105,8 +106,31 @@ Content-length: 106 } } ``` + The server responds with a success status code and no payload: ```http HTTP/1.1 204 No Content ``` +### Updating a viewpoint using `PATCH` method + +The following example shows how to mark a topic with the `viewpoint` label as reviewed for a user by using the `PATCH` method (this example does not represent an actual API, but only an illustration): + +``` +PATCH https://graph.microsoft.com/beta/sampleTopics/19:7d898072-792c-4006-bb10-5ca9f259 + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "title": "Announcements: Changes to PowerPoint and Word to open files faster", + ... + "viewpoint": { + "isReviewed" : "true" + } +} +The server responds with a success status code and no payload: + +```http +HTTP/1.1 204 No Content +``` \ No newline at end of file From 512bd54aef00f2e8ee4bf61601ce924438db0e99 Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Thu, 24 Aug 2023 19:09:29 +0100 Subject: [PATCH 569/729] Addressed feedback from OlgaPodo --- graph/patterns/upsert.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/graph/patterns/upsert.md b/graph/patterns/upsert.md index b183154d..e7466b24 100644 --- a/graph/patterns/upsert.md +++ b/graph/patterns/upsert.md @@ -2,11 +2,11 @@ Microsoft Graph API Design Pattern -*The `UPSERT` pattern is a non-destructive idempotent operation using a client-provided key, that ensures that system resources can be deployed in a reliable, repeatable, and controlled way, typically used in Infrastructure as Code (IaC) scenarios.* +*The `Upsert` pattern is a non-destructive idempotent operation using a client-provided key, that ensures that system resources can be deployed in a reliable, repeatable, and controlled way, typically used in Infrastructure as Code (IaC) scenarios.* ## Problem -Infrastructure as code (IaC) defines system resources and topologies in a descriptive manner that allows teams to manage those resources as they would code. +Infrastructure as code (IaC) defines system resources and topologies in a declarative manner that allows teams to manage those resources as they would code. Practicing IaC helps teams deploy system resources in a reliable, repeatable, and controlled way. IaC also helps automate deployment and reduces the risk of human error, especially for complex large environments. Customers want to adopt IaC practices for many of the resources managed through Microsoft Graph. @@ -19,20 +19,20 @@ Additionally, IaC code scripts or templates usually employ client-provided names ## Solution -The solution is to use an `UPSERT` pattern, to solve for the non-idempotent creation and client-provided naming problems. +The solution is to use an `Upsert` pattern, to solve for the non-idempotent creation and client-provided naming problems. -* For IaC scenarios, resources must use `UPSERT` semantics with an [alternate key](./alternate-key.md): +* For IaC scenarios, resources must use `Upsert` semantics with an [alternate key](./alternate-key.md): * Use `PATCH` with a client-provided alternate key. * 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. * For an existing resource (specified by the alternate key) the service must handle this as an "update. * Any new alternate key, used for IaC scenarios, should be called `uniqueName`, if there isn't already an existing property that could be used as an alternate key. * NOTE: the service must also support `GET` using the alternate key pattern. * 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. -* If a service does not support `UPSERT`, then a `PATCH` call against a non-existent resource must result in an HTTP "409 conflict" error. +* If a service does not support `Upsert`, then a `PATCH` call against a non-existent resource must result in an HTTP "409 conflict" error. -This solution allows for existing resources that follow Microsoft Graph conventions for CRUD operations to add `UPSERT` without impacting existing apps or functionality. +This solution allows for existing resources that follow Microsoft Graph conventions for CRUD operations to add `Upsert` without impacting existing apps or functionality. -Ideally, all new entity types should support an `UPSERT` mechanism, especially if the resource is likely be used in IaC scenarios. +Ideally, all new entity types should support an `Upsert` mechanism, especially where they support control-plane APIs, or are used in admin style or IaC scenarios. ## When to use this pattern @@ -42,10 +42,10 @@ This pattern should be adopted for resources that are managed through infrastruc * The addition of this new pattern (with alternate key) does not represent a breaking change. 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. -As a result, API producers can use the `Prefer: idempotent` to require clients to opt-in to the UPSERT behavior. +As a result, API producers can use the `Prefer: idempotent` to require clients to opt-in to the Upsert behavior. * 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. * 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. -* 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. +* API producers could 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. * 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. ```xml @@ -94,6 +94,7 @@ Create a new group, with a `uniqueName` of "Group157". In this case, this group ```http PATCH /groups(uniqueName='Group157') +Prefer: idempotent; return=representation ``` ```json @@ -107,6 +108,7 @@ Response: ```http 201 created +Preference-Applied: idempotent; return=representation ``` ```json @@ -124,6 +126,7 @@ Create a new group, with a `uniqueName` of "Group157", exactly like before. Exce ```http PATCH /groups(uniqueName='Group157') +Prefer: idempotent; return=representation ``` ```json @@ -137,6 +140,7 @@ Response: ```http 200 ok +Preference-Applied: idempotent; return=representation ``` ```json @@ -153,10 +157,11 @@ Notice how this operation is idempotent in nature, rather than returning a 409 c ### Upsert not supported Create a new group, with a `uniqueName` of "Group157". In this case, this group does not exist and additionally -the service does not `UPSERT` for groups. +the service does not `Upsert` for groups. ```http PATCH /groups(uniqueName='Group157') +Prefer: idempotent; return=representation ``` ```json From e0a308f73dedb8247efc08b79a330890caa81304 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 08:54:03 -0600 Subject: [PATCH 570/729] add paragraph about tri state --- graph/GuidelinesGraph.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index bddd4c0b..39e3da0f 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -398,6 +398,36 @@ By using an evolvable enum, instead, all we need to do is to add new members: ``` +Similarly speaking, if you find yourself using a `nullable` Enum, that is a indication that maybe what you are trying to model is something that has 3 states and an enum is more appropraite. For instance, let's assume we have a boolean property called `syncEnabled`, where `null` means that the value is undefined and inherited from the general tenant configuration. Instead of modelling like a boolean: + +```xml + +``` + +An enum not only better conveys the message: + +```xml + + + + + + +``` + +but it is also open for future scenarios: + +```diff + + + + + ++ + +``` + + ## API contract and non-backward compatible changes From 69b697e577d8db4c908a370dabcd0585495a1b81 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 09:10:37 -0600 Subject: [PATCH 571/729] add flag/collection enum --- graph/GuidelinesGraph.md | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 39e3da0f..5a238a3e 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -3,8 +3,6 @@ Table of contents - [Microsoft Graph REST API Guidelines](#microsoft-graph-rest-api-guidelines) - - [](#) - - [History](#history) - [Introduction](#introduction) - [Legend](#legend) - [Design approach](#design-approach) @@ -21,15 +19,6 @@ Table of contents - [Recommended API design patterns](#recommended-api-design-patterns) - [References](#references) -## - -#### History - -| Date | Notes | -|-------------|-----------------------------| -| 2022-Jun-14 | Edit pass for formatting, links | -| 2021-Sep-28 | Using summary and patterns style | -| 2020-Oct-04 | Initial version in Wiki | ## Introduction @@ -427,7 +416,30 @@ but it is also open for future scenarios: ``` +#### Flag Enums or Collection of Enums + +In case an enum can have multiple values at the same time the tentation is to model the property as a collection of Enums: + +```xml + +``` + +However, [Flagged Enums](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#_Toc38530378) can model this use case scenario: + +```diff +- ++ + + + +- ++ + +``` + +With such enum, customers can select multiple values in a single field: +`displayMethod = tip | alert` ## API contract and non-backward compatible changes From 54779a9528715f9f4173dd1ae4caaedc5e464bab Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 09:21:54 -0600 Subject: [PATCH 572/729] nullable enum --- graph/GuidelinesGraph.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 5a238a3e..0b9f6a5d 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -416,6 +416,8 @@ but it is also open for future scenarios: ``` +Additionally speaking, depending on the situation, a nullable enum can very likely be avoided by adding a `none` member. + #### Flag Enums or Collection of Enums In case an enum can have multiple values at the same time the tentation is to model the property as a collection of Enums: From f02ded5fd242db933aa50e7686e88c099ef18789 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 25 Aug 2023 12:08:31 -0400 Subject: [PATCH 573/729] formatting --- graph/patterns/viewpoint.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index 84927dff..b7f13c13 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -129,6 +129,8 @@ Content-Type: application/json "isReviewed" : "true" } } +``` + The server responds with a success status code and no payload: ```http From d28bce6b9a6dd8cefe2ae154e4dc8721e610f29b Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 25 Aug 2023 12:13:51 -0400 Subject: [PATCH 574/729] Update viewpoint.md --- graph/patterns/viewpoint.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index b7f13c13..226a8042 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -72,7 +72,7 @@ Content-type: application/json "chatType": "meeting", "viewpoint":{ "lastMessageReadDateTime": "2021-03-28T21:10:00.000Z" - // User has unread messages + /* User has unread messages */ } }, { @@ -83,7 +83,7 @@ Content-type: application/json "chatType": "group", "viewpoint":{ "lastMessageReadDateTime": "0000-01-01T00:00:00.000Z" - // User hasnt read anything since no message was posted + /* User hasnt read anything since no message was posted */ } } ] @@ -135,4 +135,4 @@ The server responds with a success status code and no payload: ```http HTTP/1.1 204 No Content -``` \ No newline at end of file +``` From a287ea1ef6b5be70bbc566ee1472474f73f47baa Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 10:20:24 -0600 Subject: [PATCH 575/729] flag enum stars with 1 --- graph/GuidelinesGraph.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 0b9f6a5d..7085e292 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -431,11 +431,14 @@ However, [Flagged Enums](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/ ```diff - + - - - +- ++ +- ++ +- ++ - -+ ++ ``` From 4d13b270bd552a92fb16faa0c89bdd0640d2d9bf Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 25 Aug 2023 12:21:54 -0400 Subject: [PATCH 576/729] Update viewpoint.md --- graph/patterns/viewpoint.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index 226a8042..0db7bd80 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -68,11 +68,10 @@ Content-type: application/json "id": "19:meeting_MjdhNjM4YzUtYzExZi00OTFkLTkzZTAtNTVlNmZmMDhkNGU2@thread.v2", "topic": "Meeting chat sample", "createdDateTime": "2020-12-08T23:53:05.801Z", - "lastUpdatedDateTime": "2020-12-08T23:58:32.511Z", + "lastUpdatedDateTime": "2022-12-08T23:58:32.511Z", "chatType": "meeting", "viewpoint":{ - "lastMessageReadDateTime": "2021-03-28T21:10:00.000Z" - /* User has unread messages */ + "lastMessageReadDateTime": "2021-03-28T21:10:00.000Z" } }, { @@ -82,8 +81,7 @@ Content-type: application/json "lastUpdatedDateTime": "2020-12-08T23:53:11.012Z", "chatType": "group", "viewpoint":{ - "lastMessageReadDateTime": "0000-01-01T00:00:00.000Z" - /* User hasnt read anything since no message was posted */ + "lastMessageReadDateTime": "0000-01-01T00:00:00.000Z" } } ] From e32861bfc09fb46adfffdc6a5070508d53a4d037 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 25 Aug 2023 12:25:39 -0400 Subject: [PATCH 577/729] Update viewpoint.md --- graph/patterns/viewpoint.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index 0db7bd80..0442c45c 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -13,7 +13,7 @@ The viewpoint pattern provides a solution to how to model an individual user con For example, the `viewpoint` property can indicate whether a message is read, deleted, or flagged for a given user. The consistent naming convention ensures that when a developer uses Graph APIs all ‘viewpoints’ structural properties represent type specific user context across different M365 services and features. -This pattern simplifies the API client logic by hiding the state transition details and providing state persistency on the server side. The server can manage the different viewpoints for the shared resource without exposing additional complexity to the client.To support queries for a user state the `viewpoint` property should support filtering. +This pattern simplifies the API client logic by hiding the state transition details and providing state persistency on the server side. The server can manage the different viewpoints for the shared resource without exposing additional complexity to the client. To support queries for a user state the `viewpoint` property should support filtering. ## Issues and considerations - Because the `viewpoint` property reflects an individual user's context, it is null when accessed with application permissions. From f5f9684b72eb5ea0252dea881fddd56314d1b627 Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Fri, 25 Aug 2023 17:50:30 +0100 Subject: [PATCH 578/729] Updated guidelines for Upsert --- graph/GuidelinesGraph.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e0fecabd..e98083a4 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -256,7 +256,8 @@ If possible, APIs SHOULD use resource-based designs with standard HTTP methods r | Microsoft Graph rules for modeling behavior | |------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.
    This approach requires the server to produce system generated identities. | +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.
    This approach requires the server to produce a system generated key. | +| :heavy_check_mark: **MAY** use PATCH to create new entities in insertable entity sets or collections.
    This [Upsert](./patterns/upsert.md) approach requires the caller to provide a key. | | :heavy_check_mark: **MUST** use PATCH to edit updatable resources. | | :heavy_check_mark: **MUST** use DELETE to delete deletable resources. | | :heavy_check_mark: **MUST** use GET for listing and reading resources. | @@ -389,6 +390,7 @@ The guidelines in previous sections are intentionally brief and provide a jump s | [Navigation properties](./patterns/navigation-property.md) | Model resource relationships | | [Operations](./patterns/operations.md) | Model complex business operations | | [Type hierarchy](./patterns/subtypes.md) | Model `is-a` relationships using subtypes. | +| [Upsert](./patterns/upsert.md) | Idempotent operation to create or update a resource using a client-provided key. | ## References From b415a081b04eef01d0ab63ed66efccf3ee9c4c4c Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 25 Aug 2023 12:58:12 -0400 Subject: [PATCH 579/729] added approvers for graph --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 06bf5c02..f77559c4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,3 @@ # These are the set of folks who should review PRs on the azureRestUpdates branch. * @microsoft/azure-api-stewardship-board @Azure/api-stewardship-board +/graph/* @microsoft/graphguidelinesapprovers From f9a8778316c4ff4e4d806429c8ac825e21c64c1d Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 11:01:18 -0600 Subject: [PATCH 580/729] why do we need quotes? --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8d6e5058..efe88feb 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -> # NOTICE TO READERS -> -> ## Guidance for Azure service teams -> Azure service teams should use companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. -> -> ## Guidance for Microsoft Graph service teams -> Microsoft Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. -> -> In the process of building many of Microsoft's highest scale services, the Microsoft Graph team found the Microsoft API guidelines tremendously useful as a baseline. However, there are several areas where we need to provide more clarity on how developers should describe their APIs. The companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) is a set of amendments and clarifications for Microsoft Graph that act as further reading. Recognizing that two documents is a lot for a new API designer to absorb, our plan is to follow the approach Azure have taken and roll out guidelines for Microsoft Graph into a single consolidated document. + # NOTICE TO READERS + + ## Guidance for Azure service teams + Azure service teams should use companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. + + ## Guidance for Microsoft Graph service teams + Microsoft Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. + + In the process of building many of Microsoft's highest scale services, the Microsoft Graph team found the Microsoft API guidelines tremendously useful as a baseline. However, there are several areas where we need to provide more clarity on how developers should describe their APIs. The companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) is a set of amendments and clarifications for Microsoft Graph that act as further reading. Recognizing that two documents is a lot for a new API designer to absorb, our plan is to follow the approach Azure have taken and roll out guidelines for Microsoft Graph into a single consolidated document. --- From 1c895a7f7b735a84a19d7971eac229a7ab41336d Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 11:02:17 -0600 Subject: [PATCH 581/729] no spaces --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index efe88feb..8c88ec68 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # NOTICE TO READERS ## Guidance for Azure service teams - Azure service teams should use companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. +Azure service teams should use companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. - ## Guidance for Microsoft Graph service teams +## Guidance for Microsoft Graph service teams Microsoft Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. - In the process of building many of Microsoft's highest scale services, the Microsoft Graph team found the Microsoft API guidelines tremendously useful as a baseline. However, there are several areas where we need to provide more clarity on how developers should describe their APIs. The companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) is a set of amendments and clarifications for Microsoft Graph that act as further reading. Recognizing that two documents is a lot for a new API designer to absorb, our plan is to follow the approach Azure have taken and roll out guidelines for Microsoft Graph into a single consolidated document. +In the process of building many of Microsoft's highest scale services, the Microsoft Graph team found the Microsoft API guidelines tremendously useful as a baseline. However, there are several areas where we need to provide more clarity on how developers should describe their APIs. The companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) is a set of amendments and clarifications for Microsoft Graph that act as further reading. Recognizing that two documents is a lot for a new API designer to absorb, our plan is to follow the approach Azure have taken and roll out guidelines for Microsoft Graph into a single consolidated document. --- From 06b15791bae6675eb5685c117d7aeeecaef7f4aa Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Fri, 25 Aug 2023 13:25:28 -0400 Subject: [PATCH 582/729] Update graph/patterns/viewpoint.md Co-authored-by: Dan Kershaw [MSFT] --- graph/patterns/viewpoint.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index 0442c45c..552e222a 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -11,7 +11,7 @@ A shared resource, such as a website or a group message, may have different stat The viewpoint pattern provides a solution to how to model an individual user context on a shared resource using a `viewpoint` structural property on an API entity type. For example, the `viewpoint` property can indicate whether a message is read, deleted, or flagged for a given user. -The consistent naming convention ensures that when a developer uses Graph APIs all ‘viewpoints’ structural properties represent type specific user context across different M365 services and features. +The consistent naming convention ensures that when a developer uses Graph APIs all `viewpoint` structural properties represent type specific user context across different M365 services and features. This pattern simplifies the API client logic by hiding the state transition details and providing state persistency on the server side. The server can manage the different viewpoints for the shared resource without exposing additional complexity to the client. To support queries for a user state the `viewpoint` property should support filtering. ## Issues and considerations From 42f1626047a8a9fd9f76ab2ab0b3f781afba6714 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 11:40:58 -0600 Subject: [PATCH 583/729] rephrase --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 7085e292..8294caac 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -318,7 +318,7 @@ For a complete mapping of error codes to HTTP statuses, see ### Enums -In OData, enum are nominal types representing a subset of the nominal type they use, and are especially useful in cases where certain properties have predefined, limited options. +In OData, enums represent a subset of the nominal type they rely on, and are especially useful in cases where certain properties have predefined, limited options. ```xml From c03614707a5284b188f5d62574ee98861305d415 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 11:41:35 -0600 Subject: [PATCH 584/729] Update graph/GuidelinesGraph.md Co-authored-by: Dan Kershaw [MSFT] --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 8294caac..ebcc7161 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -339,7 +339,7 @@ In OData, enums represent a subset of the nominal type they rely on, and are esp #### Enum or Booleans -Enumerations are a good alternative to booleans when one of the two values (`true`, `false`) conveyes other possible values not yet conceived. Let's assume we have an `Error` type and a property to communicate how to display it: +Enumerations are a good alternative to Booleans when one of the two values (`true`, `false`) conveys other possible values not yet conceived. Let's assume we have an `Error` type and a property to communicate how to display it: ```xml From e35e69182924ec2e55cc5990582bab2d7ee1705a Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 12:31:31 -0600 Subject: [PATCH 585/729] move to different file --- graph/GuidelinesGraph.md | 131 +----------------------------- graph/patterns/enums.md | 129 +++++++++++++++++++++++++++++ graph/patterns/evolvable-enums.md | 2 +- 3 files changed, 133 insertions(+), 129 deletions(-) create mode 100644 graph/patterns/enums.md diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index fc26a637..1a642143 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -191,6 +191,9 @@ The three most often used patterns in Microsoft Graph today are type hierarchy, - **[Flat bag of properties](./patterns/flat-bag.md)** is represented by one entity type with all the potential properties plus an additional property to distinguish the variants, often called type. The type property describes the variant and also defines properties that are required or meaningful for the variant given by the type property. +- **[Enums](./patterns/enums.md)** represent a subset of the nominal type they rely on, and are especially useful in cases where certain properties have predefined, limited options. + + The following table shows a summary of the main qualities for each pattern and can help you select a pattern fit for your use case. | API qualities\patterns | Properties and behavior described in metadata | Supports combinations of properties and behaviors | Simple query construction | @@ -319,135 +322,7 @@ For a complete mapping of error codes to HTTP statuses, see -### Enums - -In OData, enums represent a subset of the nominal type they rely on, and are especially useful in cases where certain properties have predefined, limited options. - -```xml - - - - - -``` - -#### Pros - -- Our SDK generators will translate the enum to the best representation of the target programming language, resulting in a better developer experience and free client side validation - -#### Cons - -- Adding a new value requires to go through a (generally fast) API Review -- If the enum is not [evolvable](./patterns/evolvable-enums.md), adding a new value is a breaking change and will generally not be allowed - -#### Enum or Booleans - -Enumerations are a good alternative to Booleans when one of the two values (`true`, `false`) conveys other possible values not yet conceived. Let's assume we have an `Error` type and a property to communicate how to display it: - -```xml - - - - - -``` - -The `false` value here merely communicates that the error shall not be displayed as a tip. What if, in the future, the error could be displayed as a `tip` or `alert`, and then in a more distant future, a `dialog` option is viable? - -With the current model, the only way is to add more boolean properties to convey the new information: - -```diff - - - - -+ -+ - -``` - -Additionally speaking, the workload will now also have to validate the data structure and make sure that only one of the 3 values is `true` - -By using an evolvable enum, instead, all we need to do is to add new members: - -```diff - - - -+ -- -- -- - -``` - -```xml - - - - - - -``` - -Similarly speaking, if you find yourself using a `nullable` Enum, that is a indication that maybe what you are trying to model is something that has 3 states and an enum is more appropraite. For instance, let's assume we have a boolean property called `syncEnabled`, where `null` means that the value is undefined and inherited from the general tenant configuration. Instead of modelling like a boolean: - -```xml - -``` - -An enum not only better conveys the message: - -```xml - - - - - - -``` - -but it is also open for future scenarios: - -```diff - - - - - -+ - -``` - -Additionally speaking, depending on the situation, a nullable enum can very likely be avoided by adding a `none` member. - -#### Flag Enums or Collection of Enums - -In case an enum can have multiple values at the same time the tentation is to model the property as a collection of Enums: - -```xml - -``` - -However, [Flagged Enums](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#_Toc38530378) can model this use case scenario: - -```diff -- -+ -- -+ -- -+ -- -+ -- -+ - -``` - -With such enum, customers can select multiple values in a single field: -`displayMethod = tip | alert` ## API contract and non-backward compatible changes diff --git a/graph/patterns/enums.md b/graph/patterns/enums.md new file mode 100644 index 00000000..f296d9a3 --- /dev/null +++ b/graph/patterns/enums.md @@ -0,0 +1,129 @@ +### Enums + +In OData, enums represent a subset of the nominal type they rely on, and are especially useful in cases where certain properties have predefined, limited options. + +```xml + + + + + +``` + +#### Pros + +- Our SDK generators will translate the enum to the best representation of the target programming language, resulting in a better developer experience and free client side validation + +#### Cons + +- Adding a new value requires to go through a (generally fast) API Review +- If the enum is not [evolvable](./patterns/evolvable-enums.md), adding a new value is a breaking change and will generally not be allowed + +#### Enum or Booleans + +Enumerations are a good alternative to Booleans when one of the two values (`true`, `false`) conveys other possible values not yet conceived. Let's assume we have an `Error` type and a property to communicate how to display it: + +```xml + + + + + +``` + +The `false` value here merely communicates that the error shall not be displayed as a tip. What if, in the future, the error could be displayed as a `tip` or `alert`, and then in a more distant future, a `dialog` option is viable? + +With the current model, the only way is to add more boolean properties to convey the new information: + +```diff + + + + ++ ++ + +``` + +Additionally speaking, the workload will now also have to validate the data structure and make sure that only one of the 3 values is `true` + +By using an evolvable enum, instead, all we need to do is to add new members: + +```diff + + + ++ +- +- +- + +``` + +```xml + + + + + + +``` + +Similarly speaking, if you find yourself using a `nullable` Enum, that is a indication that maybe what you are trying to model is something that has 3 states and an enum is more appropraite. For instance, let's assume we have a boolean property called `syncEnabled`, where `null` means that the value is undefined and inherited from the general tenant configuration. Instead of modelling like a boolean: + +```xml + +``` + +An enum not only better conveys the message: + +```xml + + + + + + +``` + +but it is also open for future scenarios: + +```diff + + + + + ++ + +``` + +Additionally speaking, depending on the situation, a nullable enum can very likely be avoided by adding a `none` member. + +#### Flag Enums or Collection of Enums + +In case an enum can have multiple values at the same time the tentation is to model the property as a collection of Enums: + +```xml + +``` + +However, [Flagged Enums](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#_Toc38530378) can model this use case scenario: + +```diff +- ++ +- ++ +- ++ +- ++ +- ++ + +``` + +With such enum, customers can select multiple values in a single field: + +`displayMethod = tip | alert` diff --git a/graph/patterns/evolvable-enums.md b/graph/patterns/evolvable-enums.md index 6d2defbf..834575dc 100644 --- a/graph/patterns/evolvable-enums.md +++ b/graph/patterns/evolvable-enums.md @@ -4,7 +4,7 @@ Microsoft Graph API Design Pattern *The evolvable enums pattern allows API producers to extend enumerated types with new members without breaking API consumers.* -Note: You might be interested in reading the [Enum guidance](../GuidelinesGraph.md#enums) first +Note: You might be interested in reading the [Enum guidance](./enums.md) first ## Problem From 25ec81d0a3ec3a96a5a0ebf0e397c2b9c41b9b12 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 12:32:09 -0600 Subject: [PATCH 586/729] removenewline --- graph/GuidelinesGraph.md | 1 - 1 file changed, 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 1a642143..f5ae97e2 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -323,7 +323,6 @@ For a complete mapping of error codes to HTTP statuses, see - ## API contract and non-backward compatible changes The Microsoft Graph definition of breaking changes is based on the From 15c2f827fb92bdf28619e7a8d4b403635e1f8e5b Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 12:32:33 -0600 Subject: [PATCH 587/729] remove new line --- graph/GuidelinesGraph.md | 1 - 1 file changed, 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index f5ae97e2..3c31f007 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -193,7 +193,6 @@ The three most often used patterns in Microsoft Graph today are type hierarchy, - **[Enums](./patterns/enums.md)** represent a subset of the nominal type they rely on, and are especially useful in cases where certain properties have predefined, limited options. - The following table shows a summary of the main qualities for each pattern and can help you select a pattern fit for your use case. | API qualities\patterns | Properties and behavior described in metadata | Supports combinations of properties and behaviors | Simple query construction | From 93e6fceb7975565b61b5e723ec17af29e35436f7 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 12:33:10 -0600 Subject: [PATCH 588/729] remove new line --- graph/GuidelinesGraph.md | 1 - 1 file changed, 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 3c31f007..fa3aa2b5 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -321,7 +321,6 @@ For a complete mapping of error codes to HTTP statuses, see - ## API contract and non-backward compatible changes The Microsoft Graph definition of breaking changes is based on the From fe79d6c24131deefedd1f2fa9cb175ac6ab138b7 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 12:34:10 -0600 Subject: [PATCH 589/729] lower case --- graph/patterns/enums.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph/patterns/enums.md b/graph/patterns/enums.md index f296d9a3..61eb4cee 100644 --- a/graph/patterns/enums.md +++ b/graph/patterns/enums.md @@ -3,7 +3,7 @@ In OData, enums represent a subset of the nominal type they rely on, and are especially useful in cases where certain properties have predefined, limited options. ```xml - + @@ -24,7 +24,7 @@ In OData, enums represent a subset of the nominal type they rely on, and are esp Enumerations are a good alternative to Booleans when one of the two values (`true`, `false`) conveys other possible values not yet conceived. Let's assume we have an `Error` type and a property to communicate how to display it: ```xml - + @@ -36,7 +36,7 @@ The `false` value here merely communicates that the error shall not be displayed With the current model, the only way is to add more boolean properties to convey the new information: ```diff - + @@ -50,7 +50,7 @@ Additionally speaking, the workload will now also have to validate the data stru By using an evolvable enum, instead, all we need to do is to add new members: ```diff - + + From 50481c1c9f7f308a0deb5d9d51af850154c47029 Mon Sep 17 00:00:00 2001 From: Vincenzo Chianese Date: Fri, 25 Aug 2023 14:31:31 -0600 Subject: [PATCH 590/729] call it public notification --- graph/patterns/enums.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graph/patterns/enums.md b/graph/patterns/enums.md index 61eb4cee..a47de3ea 100644 --- a/graph/patterns/enums.md +++ b/graph/patterns/enums.md @@ -21,22 +21,22 @@ In OData, enums represent a subset of the nominal type they rely on, and are esp #### Enum or Booleans -Enumerations are a good alternative to Booleans when one of the two values (`true`, `false`) conveys other possible values not yet conceived. Let's assume we have an `Error` type and a property to communicate how to display it: +Enumerations are a good alternative to Booleans when one of the two values (`true`, `false`) conveys other possible values not yet conceived. Let's assume we have an `publicNotification` type and a property to communicate how to display it: ```xml - + ``` -The `false` value here merely communicates that the error shall not be displayed as a tip. What if, in the future, the error could be displayed as a `tip` or `alert`, and then in a more distant future, a `dialog` option is viable? +The `false` value here merely communicates that the notification shall not be displayed as a tip. What if, in the future, the notification could be displayed as a `tip` or `alert`, and then in a more distant future, a `dialog` option is viable? With the current model, the only way is to add more boolean properties to convey the new information: ```diff - + @@ -50,7 +50,7 @@ Additionally speaking, the workload will now also have to validate the data stru By using an evolvable enum, instead, all we need to do is to add new members: ```diff - + + From 0d8f4e4308ceddd96759c15c3f8ee2991b62817f Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 28 Aug 2023 16:46:54 -0700 Subject: [PATCH 591/729] Update subsets.md --- graph/patterns/subsets.md | 51 +++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/graph/patterns/subsets.md b/graph/patterns/subsets.md index c4f0a602..09425368 100644 --- a/graph/patterns/subsets.md +++ b/graph/patterns/subsets.md @@ -185,33 +185,52 @@ _Note: Unrelated properties on entities are omitted for easier readability._ ### Filter when base type has the "kind" enum property -```HTTP -GET .../memberships?$filter=membershipKind eq 'all' +```http +GET https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies?$filter=conditions/users/includeGuestsOrExternalUsers/externalTenants/membershipKind eq 'all' 200 OK { - "value": [ - { - "@odata.type": "#microsoft.graph.allMembership", - "membershipKind": "all" - }, - ... - ] + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#conditionalAccessPolicy", + "values": [ + { + "id": "66d36273-fe4c-d478-dc22-e0179d856ce7", + "conditions": { + "users": { + "includeGuestsOrExternalUsers": { + "externalTenants": { + "@odata.type":"microsoft.graph.conditionalAccessAllExternalTenants", + "membershipKind": "all" + } + } + } + } + } + ] } ``` ### Filter when base type lacks the "kind" enum property ```HTTP -GET .../memberships?$filter=isof(microsoft.graph.allMembership) +GET https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies?$filter=isof(conditions/users/includeGuestsOrExternalUsers/externalTenants, microsoft.graph.conditionalAccessAllExternalTenants) 200 OK { - "value": [ - { - "@odata.type": "#microsoft.graph.allMembership", - }, - ... - ] + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#conditionalAccessPolicy", + "values": [ + { + "id": "66d36273-fe4c-d478-dc22-e0179d856ce7", + "conditions": { + "users": { + "includeGuestsOrExternalUsers": { + "externalTenants": { + "@odata.type":"microsoft.graph.conditionalAccessAllExternalTenants", + "membershipKind": "all" + } + } + } + } + } + ] } ``` From 9f092bdd1fdc38bc1b88c8f6931fef889af19b28 Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Tue, 29 Aug 2023 20:25:18 +0100 Subject: [PATCH 592/729] Addressed a couple of comments --- graph/GuidelinesGraph.md | 2 +- .../patterns/default-properties-and-select.md | 30 ++++++++++++++++++ graph/patterns/upsert.md | 31 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 graph/patterns/default-properties-and-select.md diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e98083a4..06381e62 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -257,7 +257,7 @@ If possible, APIs SHOULD use resource-based designs with standard HTTP methods r | Microsoft Graph rules for modeling behavior | |------------------------------------------------------------------| | :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.
    This approach requires the server to produce a system generated key. | -| :heavy_check_mark: **MAY** use PATCH to create new entities in insertable entity sets or collections.
    This [Upsert](./patterns/upsert.md) approach requires the caller to provide a key. | +| :ballot_box_with_check: **SHOULD** additionally use PATCH to create new entities in insertable entity sets or collections.
    This [Upsert](./patterns/upsert.md) approach requires the caller to provide a key. | | :heavy_check_mark: **MUST** use PATCH to edit updatable resources. | | :heavy_check_mark: **MUST** use DELETE to delete deletable resources. | | :heavy_check_mark: **MUST** use GET for listing and reading resources. | diff --git a/graph/patterns/default-properties-and-select.md b/graph/patterns/default-properties-and-select.md new file mode 100644 index 00000000..57ad1ce8 --- /dev/null +++ b/graph/patterns/default-properties-and-select.md @@ -0,0 +1,30 @@ +# Default properties and $select + +Microsoft Graph API Design Pattern + +*Provide a short description of the pattern.* + + +## Problem + +*Describe the business context relevant for the pattern.* + +*Provide a short description of the problem.* + +## Solution + +*Describe how to implement the solution to solve the problem.* + +*Describe related patterns.* + +## When to use this pattern + +*Describe when and why the solution is applicable and when it might not be.* + +## Issues and considerations + +*Describe tradeoffs of the solution.* + +## Example + +*Provide a short example from real life.* diff --git a/graph/patterns/upsert.md b/graph/patterns/upsert.md index e7466b24..6db1632a 100644 --- a/graph/patterns/upsert.md +++ b/graph/patterns/upsert.md @@ -154,6 +154,37 @@ Preference-Applied: idempotent; return=representation Notice how this operation is idempotent in nature, rather than returning a 409 conflict error. +### Updating a record + +Update "Group157" group with a new description. + +```http +PATCH /groups(uniqueName='Group157') +Prefer: idempotent; return=representation +``` + +```json +{ + "description": "Some of my favorite people in the world." +} +``` + +Response: + +```http +200 ok +Preference-Applied: idempotent; return=representation +``` + +```json +{ + "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66", + "displayName": "My favorite group", + "description": "Some of my favorite people in the world.", + "uniqueName": "Group157" +} +``` + ### Upsert not supported Create a new group, with a `uniqueName` of "Group157". In this case, this group does not exist and additionally From 8c215e37a0958c110938171456c91e07a56f262c Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Tue, 29 Aug 2023 20:28:02 +0100 Subject: [PATCH 593/729] Removed file that's in a different PR --- .../patterns/default-properties-and-select.md | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 graph/patterns/default-properties-and-select.md diff --git a/graph/patterns/default-properties-and-select.md b/graph/patterns/default-properties-and-select.md deleted file mode 100644 index 57ad1ce8..00000000 --- a/graph/patterns/default-properties-and-select.md +++ /dev/null @@ -1,30 +0,0 @@ -# Default properties and $select - -Microsoft Graph API Design Pattern - -*Provide a short description of the pattern.* - - -## Problem - -*Describe the business context relevant for the pattern.* - -*Provide a short description of the problem.* - -## Solution - -*Describe how to implement the solution to solve the problem.* - -*Describe related patterns.* - -## When to use this pattern - -*Describe when and why the solution is applicable and when it might not be.* - -## Issues and considerations - -*Describe tradeoffs of the solution.* - -## Example - -*Provide a short example from real life.* From 9f5ef0e01137c7061e15d66a0b80c52f106b694c Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Wed, 30 Aug 2023 21:35:07 +0100 Subject: [PATCH 594/729] New default properties pattern --- graph/GuidelinesGraph.md | 1 + graph/patterns/default-properties.md | 201 +++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 graph/patterns/default-properties.md diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index a5722d0f..7371ceb7 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -380,6 +380,7 @@ The guidelines in previous sections are intentionally brief and provide a jump s | [Alternate key](./patterns/alternate-key.md) | Uniquely identify and query resources using an alternate key. | | [Change tracking](./patterns/change-tracking.md) | Keep API consumers in sync with changes without polling. | | [Collection subsets](./patterns/subsets.md) | Model collection subsets | +| [Default properties](./patterns/default-properties.md) | Omit non-default properties from the response unless they are explicitly requested using `$select`. | [Dictionary](./patterns/dictionary.md) | Clients can provide an unknown quantity of data elements of the same type. | | [Evolvable enums](./patterns/evolvable-enums.md) | Extend enumerated types without breaking changes. | | [Facets](./patterns/facets.md) | Model parent-child relationships. | diff --git a/graph/patterns/default-properties.md b/graph/patterns/default-properties.md new file mode 100644 index 00000000..0736dc4a --- /dev/null +++ b/graph/patterns/default-properties.md @@ -0,0 +1,201 @@ +# Default properties + +Microsoft Graph API Design Pattern + +*The default properties pattern allows API producers to omit specific properties from the response unless they are explicitly requested using `$select`. The pattern also uses an instance annotation to inform callers that other properties are also available. The same annotation is also use to encourage callers to use `$select`.* + +## Problem + +API producers want to control the set of properties that their entities return by default, when the incoming request does not specify a `$select`. This can be desirable when an entity type has many properties or an API producer needs to add properties that are computationally expensive to return by default. + +## Solution + +For incoming requests targeting an entity type where the caller does not specify a `$select`` clause, API producers **may** return a subset of the entity type's properties, omitting computationally expensive properties. + +To get the non-default properties of an entity type, callers must explicitly request them using `$select`. + +## When to use this pattern + +API producers should use this pattern when adding expensive or non-performant properties to an existing entity type, or when adding properties to an entity type that has already grown too large (with more than 20 properties). + +## Issues and considerations + +- Do **not** rely on the `ags:Default` schema annotation for default properties functionality, as this is a legacy implementation. Returning default properties **must** be implemented by API producers. +- Changing a default property to non-default is considered a breaking change. +- One of the challenges with default properties is informing developers that the response does not contain the full set of properties. To solve for this discovery problem, if the response contains default properties only, then: + - the response **must** contain a `@microsoft.graph.tips` instance annotation. + - the `@microsoft.graph.tips` instance annotation **must** only be emitted if the client uses "developer mode" via the `Prefer: ms-graph-dev-mode` HTTP request header. It is expected that this header will only be used by client developer and scripting tools like Graph Explorer, the Microsoft Graph Postman collections, and Microsoft Graph PowerShell. + - the `@microsoft.graph.tips` instance annotation value **must** contain "This request only returns a subset of the resource's properties. Your app will need to use $select to return non-default properties. To find out what other properties are available for this resource see https://learn.microsoft.com/graph/api/resources/{entityTypeName}". +- Callers must be able to use `$filter` with non-default properties, even though they won't show up by default in the response. + +Additionally, for incoming requests targeting an entity type where the caller does not specify a `$select` clause, the API Gateway Service will inject a `@microsoft.graph.tips` instance annotation, informing callers to use $select, when in "developer mode". +API producers who use [response passthrough](https://dev.azure.com/msazure/One/_wiki/wikis/Microsoft%20Graph%20Partners/391069/Enabling-response-passthrough) must also implement this behavior, supplying the same information as shown in the [examples section below](#calling-an-api-without-using-select). + +## Examples + +In this example we'll use the following `channel` entity type. + +```xml + + + + + + + + + + + + +``` + +In this scenario, the API producer wants to add the `moderationSettings` property to the `channel` entity type. +But when paging through 1000 channels at a time, this additional property will introduce a considerable increase in the response times. +The API producer will use the default properties pattern here, and **not** return `moderationSettings` by default. + +### Calling an API with default properties + +In this example, the caller, using Graph Explorer, does not use $select, and the API returns just the default properties. + +#### Request + +```http +GET /teams/{id}/channels +Prefer: ms-graph-dev-mode +``` + +#### Response + +```http +200 ok +Content-type: application/json +``` + +```json +{ + "@odata.context": "https://graph.microsoft.com/beta/$metadata#Collection(microsoft.graph.channel)", + "@microsoft.graph.tips": "This request only returns a subset of the resource properties. Your app will need to use $select to return non-default properties. To find out what other properties are supported for this resource, please see the Properties section in https://learn.microsoft.com/graph/api/resources/channel.", + "value": [ + { + "displayName": "My First Shared Channel", + "description": "This is my first shared channels", + "id": "19:PZC_kAPAm12RPBMkEaJyXaY_d2PE6mJV6MzO1EiCbnk1@thread.tacv2", + "membershipType": "shared", + "email": "someemail@dot.com", + "webUrl": "webUrl-value", + "filesFolderWebUrl": "sharePointUrl-value", + "tenantId": "tenantId-value", + "isFavoriteByDefault": null, + "createdDateTime": "2019-08-07T19:00:00Z" + }, + { + "displayName": "My Second Private Channel", + "description": "This is my second shared channels", + "id": "19:PZC_kAPAm12RPBMkEaJyXaY_d2PE6mJV6MzO1EiCbnk2@thread.tacv2", + "membershipType": "private", + "email": "someemail2@dot.com", + "webUrl": "webUrl-value2", + "filesFolderWebUrl": "sharePointUrl-value2", + "tenantId": "tenantId-value", + "isFavoriteByDefault": null, + "createdDateTime": "2019-08-09T19:00:00Z" + } + ] +} +``` + +In the response, we can see that `moderationSettings` is not being returned. Additionally, the API producer is returning a `tips` instance annotation, informing the caller that this response only returns default properties, how to get the non-default properties, and where to find information about this type's properties. The `tips` instance annotation is only emitted if the `Prefer: ms-graph-dev-mode` HTTP request header is present. + +### Calling an API with default properties and $select + +In this example, the caller needs `moderationSettings` for their API scenario. They try this out in Graph Explorer first. + +#### Request + +```http +GET /teams/{id}/channels?$select=id,membershipType,moderationSettings +Prefer: ms-graph-dev-mode +``` + +#### Response + +```http +200 ok +Content-type: application/json +``` + +```json +{ + "@odata.context": "https://graph.microsoft.com/beta/$metadata#Collection(microsoft.graph.channel)", + "value": [ + { + "id": "19:PZC_kAPAm12RPBMkEaJyXaY_d2PE6mJV6MzO1EiCbnk1@thread.tacv2", + "membershipType": "shared", + "channelModerationSettings": { + "userNewMessageRestriction": "everyone", + "replyRestriction": "everyone", + "allowNewMessageFromBots": true, + "allowNewMessageFromConnectors": true + } + }, + { + "id": "19:PZC_kAPAm12RPBMkEaJyXaY_d2PE6mJV6MzO1EiCbnk2@thread.tacv2", + "membershipType": "private", + "channelModerationSettings": { + "userNewMessageRestriction": "moderators", + "replyRestriction": "authorAndModerators", + "allowNewMessageFromBots": true, + "allowNewMessageFromConnectors": true + } + } + ] +} +``` + +In this case, because the request has a `$select`, the `tips` instance annotation is not emitted. + +### Calling an API without using $select + +The caller makes a `GET` request without $select, to an API that doesn't have any default properties, via Graph Explorer. + +#### Request + +```http +GET /me/todo/lists +Prefer: ms-graph-dev-mode +``` + +#### Response + +```http +200 ok +Content-type: application/json +``` + +```json +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('99a6e897-8c54-4354-a739-626fbe28ed78')/todo/lists", + "@microsoft.graph.tips": "Use $select to choose only the properties your app needs, as this can lead to performance improvements. For example: GET me/todo/lists?$select=displayName,isOwner", + "value": [ + { + "@odata.etag": "W/\"c5yMNreru0OMO71/IwuKGQAG6WUnjQ==\"", + "displayName": "Tasks", + "isOwner": true, + "isShared": false, + "wellknownListName": "defaultList", + "id": "AAMkADU3NTBhNWUzLWE0MWItNGViYy1hMTA0LTkzNjRlYTA2ZWI2ZAAuAAAAAAAFup0i-hqtR5N14AJlh2qTAQATqGUvrHrTEbWPAKDJQ2mMAAACWIG1AAA=" + }, + { + "@odata.etag": "W/\"c5yMNreru0OMO71/IwuKGQAG6WUnmQ==\"", + "displayName": "Outlook Commitments", + "isOwner": true, + "isShared": false, + "wellknownListName": "none", + "id": "AQMkADU3NTBhNWUzLWE0MWItNGViYy1hMTA0LTkzNjRlYTA2ZWI2ZAAuAAADBbqdIv4arUeTdeACZYdqkwEAc5yMNreru0OMO71-IwuKGQABWbOTpQAAAA==" + } + ] +} +``` + +Notice how for this scenario, where there are no default properties and the caller does not use `$select`, there's a `tips` instance annotation, encouraging the app developer to use `$select`. This `tips` annotation is automatically added to the response by the API gateway service, as long as the workload service doesn't use response passthrough (in which case it is the responsibility of the workload service). From 228225f5961bdfc055d9138e5cae04c4a80b0737 Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Wed, 30 Aug 2023 22:34:46 +0100 Subject: [PATCH 595/729] Addressed comments from Mike Pizzo --- graph/patterns/upsert.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/graph/patterns/upsert.md b/graph/patterns/upsert.md index 6db1632a..a193a397 100644 --- a/graph/patterns/upsert.md +++ b/graph/patterns/upsert.md @@ -21,13 +21,17 @@ Additionally, IaC code scripts or templates usually employ client-provided names The solution is to use an `Upsert` pattern, to solve for the non-idempotent creation and client-provided naming problems. -* For IaC scenarios, resources must use `Upsert` semantics with an [alternate key](./alternate-key.md): - * Use `PATCH` with a client-provided alternate key. - * 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. - * For an existing resource (specified by the alternate key) the service must handle this as an "update. - * Any new alternate key, used for IaC scenarios, should be called `uniqueName`, if there isn't already an existing property that could be used as an alternate key. +* For IaC scenarios, resources must use `Upsert` semantics with a client-provided key: + * Use `PATCH` with a client-provided key: + * If there is a natural client-provided key that can serve as the primary key, then the service should support `Upsert` with that key. + * If the primary key is service-generated, the client-provided key should use an [alternate key](./alternate-key.md) to support idempotent creation. + * 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. + * For an existing resource (specified by the client-provided key) the service must handle this as an "update". + * 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. * NOTE: the service must also support `GET` using the alternate key pattern. -* 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. +* Services should always support `POST` to the collection URL. + * For service-generated keys, this should return the server generated key. + * For client-provided keys, the client should provide the key as part of the request payload. * If a service does not support `Upsert`, then a `PATCH` call against a non-existent resource must result in an HTTP "409 conflict" error. This solution allows for existing resources that follow Microsoft Graph conventions for CRUD operations to add `Upsert` without impacting existing apps or functionality. @@ -40,12 +44,11 @@ This pattern should be adopted for resources that are managed through infrastruc ## Issues and considerations -* The addition of this new pattern (with alternate key) does not represent a breaking change. +* The addition of this new pattern (with an alternate key) to an existing API does not represent a breaking change. 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. As a result, API producers can use the `Prefer: idempotent` to require clients to opt-in to the Upsert behavior. * 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. * 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. -* API producers could 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. * 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. ```xml @@ -58,6 +61,8 @@ As a result, API producers can use the `Prefer: idempotent` to require clients t ``` +* `Upsert` can also be supported against singletons, using a `PATCH` to the singleton's URL. + ## Examples 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`). @@ -71,12 +76,12 @@ For these examples we'll use the `group` entity type, which defines both a prima - + - + - + From e2fa6583f898d140d17f2a80276bd41b4395b3de Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Wed, 30 Aug 2023 22:44:24 +0100 Subject: [PATCH 596/729] Change to guidelines based on Mike Pizzo feedback --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index d67edb3c..7570aa5a 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -250,7 +250,7 @@ If possible, APIs SHOULD use resource-based designs with standard HTTP methods r | Microsoft Graph rules for modeling behavior | |------------------------------------------------------------------| -| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.
    This approach requires the server to produce a system generated key. | +| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.
    This approach requires the service to produce a system-generated key, or for a caller to provide a key in the request payload. | | :ballot_box_with_check: **SHOULD** additionally use PATCH to create new entities in insertable entity sets or collections.
    This [Upsert](./patterns/upsert.md) approach requires the caller to provide a key. | | :heavy_check_mark: **MUST** use PATCH to edit updatable resources. | | :heavy_check_mark: **MUST** use DELETE to delete deletable resources. | From 76c4458f58efa04c2f254c7644c96961b7953f35 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:29:00 -0700 Subject: [PATCH 597/729] Update product name guidance to include the caveat about the /admin segment --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index fa3aa2b5..f4946df4 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -115,7 +115,7 @@ Following is a short summary of the most often used conventions. | :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem or userId on auditActor
    - **Wrong:** webUrl on contact when it's the companyWebUrl | | :ballot_box_with_check: **SHOULD** prefix Boolean properties with `is`, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong:** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom (subjectively more natural than the following examples)
    - **Wrong:** isNewTimeProposalsAllowed or isInvitesFromAllowed (subjectively more awkward that the preceding examples) | | :no_entry: **MUST NOT** use collection, response, or request suffixes. | - **Right:** addresses
    - **Wrong:** addressCollection | -| :no_entry: **MUST NOT** contain product names. | - **Right:** chatMessages
    - **Wrong:** teamsMessages | +| :no_entry: **MUST NOT** contain product names. | - **Right:** chatMessages
    - **Wrong:** teamsMessages
    - **NOTE** there is an exception for resources that *only* exist under the `/admin` root segment | ### Uniform Resource Locators (URLs) From 52bedf04e952ee2c9eb79cdcdb8a4cabd826d4b3 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:18:00 -0400 Subject: [PATCH 598/729] removed 200OK from an example --- graph/patterns/viewpoint.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index 552e222a..bcfda66e 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -57,9 +57,12 @@ GET https://graph.microsoft.com/v1.0/users/8b081ef6-4792-4def-b2c9-c363a1bf41d5/ ``` ```http + HTTP/1.1 200 OK Content-type: application/json +``` +``` { "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats", "@odata.count": 3, @@ -94,8 +97,6 @@ The following example shows marking a chat `viewpoint` as read for a user using ```http POST https://graph.microsoft.com/beta/chats/19:7d898072-792c-4006-bb10-5ca9f2590649_8ea0e38b-efb3-4757-924a-5f94061cf8c2@unq.gbl.spaces/markChatReadForUser -Content-Type: application/json -Content-length: 106 { "user": { @@ -117,9 +118,6 @@ The following example shows how to mark a topic with the `viewpoint` label as re ``` PATCH https://graph.microsoft.com/beta/sampleTopics/19:7d898072-792c-4006-bb10-5ca9f259 -HTTP/1.1 200 OK -Content-Type: application/json - { "title": "Announcements: Changes to PowerPoint and Word to open files faster", ... From b0902169cb11f327a4384bb04559cc24b5a3ad2b Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:19:15 -0400 Subject: [PATCH 599/729] Update viewpoint.md --- graph/patterns/viewpoint.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/viewpoint.md b/graph/patterns/viewpoint.md index bcfda66e..df271a7d 100644 --- a/graph/patterns/viewpoint.md +++ b/graph/patterns/viewpoint.md @@ -115,7 +115,7 @@ HTTP/1.1 204 No Content The following example shows how to mark a topic with the `viewpoint` label as reviewed for a user by using the `PATCH` method (this example does not represent an actual API, but only an illustration): -``` +```http PATCH https://graph.microsoft.com/beta/sampleTopics/19:7d898072-792c-4006-bb10-5ca9f259 { From 58613f694f7baa7a8a44639fefe76a9b99095f0c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 20 Sep 2023 13:17:06 -0400 Subject: [PATCH 600/729] - nit: adds languages to missing code blocks in change tracking pattern --- graph/patterns/change-tracking.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph/patterns/change-tracking.md b/graph/patterns/change-tracking.md index e94d38cc..12e83bd5 100644 --- a/graph/patterns/change-tracking.md +++ b/graph/patterns/change-tracking.md @@ -81,7 +81,7 @@ API consumers need guaranteed data integrity over the set of changes to Microsof ### Change tracking on entity set -``` +```xml @@ -97,7 +97,7 @@ API consumers need guaranteed data integrity over the set of changes to Microsof ### Change tracking on navigation property -``` +```xml @@ -157,7 +157,7 @@ GET https://graph.microsoft.com/v1.0/communications/onlineMeetings/getAllRecordi Here after the initial delta call, a user resource is updated, and there is one user added to and one removed from that user’s directReports collection. Additionally, a second user is deleted. In this case, there are no further pages of change records currently available. For detailed sequence of requests see [Change Tracking](https://learn.microsoft.com/en-us/graph/delta-query-overview?tabs=http#use-delta-query-to-track-changes-in-a-resource-collection). -``` +```http GET https://graph.microsoft.com/v1.0/users/delta?$skiptoken=pqwSUjGYvb3jQpbwVAwEL7yuI3dU1LecfkkfLPtnIjvB7XnF_yllFsCrZJ { @@ -196,4 +196,4 @@ GET https://graph.microsoft.com/v1.0/users/delta?$skiptoken=pqwSUjGYvb3jQpbwVAwE } ] } -``` \ No newline at end of file +``` From b2a7cedec6505df3b4a546cdcf30e293be46d6ab Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Wed, 20 Sep 2023 21:04:14 +0100 Subject: [PATCH 601/729] Addressed Mike Pizzo's comments --- graph/GuidelinesGraph.md | 2 +- graph/patterns/upsert.md | 86 ++++++++++++++++++++++++++++------------ 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 7570aa5a..3785a72f 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -251,7 +251,7 @@ If possible, APIs SHOULD use resource-based designs with standard HTTP methods r | Microsoft Graph rules for modeling behavior | |------------------------------------------------------------------| | :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.
    This approach requires the service to produce a system-generated key, or for a caller to provide a key in the request payload. | -| :ballot_box_with_check: **SHOULD** additionally use PATCH to create new entities in insertable entity sets or collections.
    This [Upsert](./patterns/upsert.md) approach requires the caller to provide a key. | +| :ballot_box_with_check: **SHOULD** additionally use PATCH to create new entities in insertable entity sets or collections.
    This [Upsert](./patterns/upsert.md) approach requires the caller to provide a key in the request URL. | | :heavy_check_mark: **MUST** use PATCH to edit updatable resources. | | :heavy_check_mark: **MUST** use DELETE to delete deletable resources. | | :heavy_check_mark: **MUST** use GET for listing and reading resources. | diff --git a/graph/patterns/upsert.md b/graph/patterns/upsert.md index a193a397..09aecf46 100644 --- a/graph/patterns/upsert.md +++ b/graph/patterns/upsert.md @@ -21,18 +21,18 @@ Additionally, IaC code scripts or templates usually employ client-provided names The solution is to use an `Upsert` pattern, to solve for the non-idempotent creation and client-provided naming problems. -* For IaC scenarios, resources must use `Upsert` semantics with a client-provided key: - * Use `PATCH` with a client-provided key: - * If there is a natural client-provided key that can serve as the primary key, then the service should support `Upsert` with that key. - * If the primary key is service-generated, the client-provided key should use an [alternate key](./alternate-key.md) to support idempotent creation. - * 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. +* `Upsert` uses `PATCH` with a client-provided key in the URL: + * If there is a natural client-provided key that can serve as the primary key, then the service should support `Upsert` with that key. + * If the primary key is service-generated, the client-provided key should use an [alternate key](./alternate-key.md) to support idempotent creation. + * 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. * For an existing resource (specified by the client-provided key) the service must handle this as an "update". - * 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. -* NOTE: the service must also support `GET` using the alternate key pattern. +* If using an alternate key, then + * 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. + * the service must also support `GET` using the alternate key pattern. * Services should always support `POST` to the collection URL. * For service-generated keys, this should return the server generated key. - * For client-provided keys, the client should provide the key as part of the request payload. -* If a service does not support `Upsert`, then a `PATCH` call against a non-existent resource must result in an HTTP "409 conflict" error. + * For client-provided keys, the client can provide the key as part of the request payload. +* 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. This solution allows for existing resources that follow Microsoft Graph conventions for CRUD operations to add `Upsert` without impacting existing apps or functionality. @@ -44,9 +44,12 @@ This pattern should be adopted for resources that are managed through infrastruc ## Issues and considerations -* The addition of this new pattern (with an alternate key) to an existing API does not represent a breaking change. -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. -As a result, API producers can use the `Prefer: idempotent` to require clients to opt-in to the Upsert behavior. +* Services with existing APIs that use a client-defined key that want to start supporting the `Upsert` pattern may have concerns about backwards compatibility. +API producers can require clients to opt-in to the `Upsert` pattern, by using the `Prefer: create-if-missing` HTTP request header. +* `Upsert` can also be supported against singletons, using a `PATCH` to the singleton's URL. +* Services that support `Upsert` should allow clients to use the: + * `If-Match=*` request header to explicitly treat an `Upsert` request as an update and not an insert. + * `If-None-Match=*` request header to explicitly treat an `Upsert` request as an insert and not an update. * 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. * 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. * 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 ``` -* `Upsert` can also be supported against singletons, using a `PATCH` to the singleton's URL. - ## Examples -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`). +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`). ```xml @@ -99,7 +100,7 @@ Create a new group, with a `uniqueName` of "Group157". In this case, this group ```http PATCH /groups(uniqueName='Group157') -Prefer: idempotent; return=representation +Prefer: return=representation ``` ```json @@ -113,7 +114,7 @@ Response: ```http 201 created -Preference-Applied: idempotent; return=representation +Preference-Applied: return=representation ``` ```json @@ -131,7 +132,7 @@ Create a new group, with a `uniqueName` of "Group157", exactly like before. Exce ```http PATCH /groups(uniqueName='Group157') -Prefer: idempotent; return=representation +Prefer: return=representation ``` ```json @@ -145,7 +146,7 @@ Response: ```http 200 ok -Preference-Applied: idempotent; return=representation +Preference-Applied: return=representation ``` ```json @@ -165,7 +166,7 @@ Update "Group157" group with a new description. ```http PATCH /groups(uniqueName='Group157') -Prefer: idempotent; return=representation +Prefer: return=representation ``` ```json @@ -178,7 +179,7 @@ Response: ```http 200 ok -Preference-Applied: idempotent; return=representation +Preference-Applied: return=representation ``` ```json @@ -190,14 +191,47 @@ Preference-Applied: idempotent; return=representation } ``` -### Upsert not supported +### Upsert opt-in request + +In this case, the group API is a pre-existing API that supports `PATCH` with a client-provided alternate key. To enable `Upsert` behavior, +the client must opt-in using an HTTP request header, to create a new group using `PATCH`. + +```http +PATCH /groups(uniqueName='Group157') +Prefer: create-if-missing; return=representation +``` + +```json +{ + "displayName": "My favorite group", + "description": "All my favorite people in the world" +} +``` + +Response: + +```http +201 created +Preference-Applied: create-if-missing; return=representation +``` + +```json +{ + "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66", + "displayName": "My favorite group", + "description": "All my favorite people in the world", + "uniqueName": "Group157" +} +``` + +### Upsert (create) not supported -Create a new group, with a `uniqueName` of "Group157". In this case, this group does not exist and additionally -the service does not `Upsert` for groups. +Following on from the last example, the same request to create a new group, with a `uniqueName` of "Group157", +without the opt-in header, results in a 404 HTTP response code. ```http PATCH /groups(uniqueName='Group157') -Prefer: idempotent; return=representation +Prefer: return=representation ``` ```json @@ -210,5 +244,5 @@ Prefer: idempotent; return=representation Response: ```http -409 conflict +404 not found ``` From e449c4f97fb879832d9596bd984d5ee6cb1b9498 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Sat, 30 Sep 2023 09:59:11 -0700 Subject: [PATCH 602/729] Add documentation explaining the different cases for required properties, nullable properties, and properties with default values (#460) --- graph/GuidelinesGraph.md | 7 +- graph/nullable.md | 311 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 graph/nullable.md diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 3785a72f..a2d9c08c 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -222,6 +222,12 @@ Following are a few pros and cons to decide which pattern to use: > **Note:** > As can be seen in a few of the pros and cons, one of the important aspects discussed here is that the API design goes beyond the syntactical aspects of the API. Therefore, it is important to plan ahead how the API evolves, lay the foundation, and allow users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change.** The different modeling patterns differ in how they express syntax and semantics and how they allow the API to evolve without breaking compatibility. For more information, see [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) later in this article. +#### Nullable properties + +The facet and flat bag approaches often require nullable properties, so it is important to still use non-nullable properties where appropriate. +Since inheritance can often remove the use of nullable properties completely, it is also important to know when nullable properties are necessary. +See [Nullable properties](./nullable.md) for more details. + ### Query support Microsoft Graph APIs should support basic query options in conformance with OData specifications and [Microsoft REST API Guidelines for error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses). @@ -241,7 +247,6 @@ The query options part of an OData URL can be quite long, potentially exceeding Another way to avoid this is to use JSON batch as described in the [Microsoft Graph batching documentation](https://docs.microsoft.com/graph/json-batching#bypassing-url-length-limitations-with-batching). - ### Behavior modeling The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request/response pattern should be one of the first implementation decisions you make. diff --git a/graph/nullable.md b/graph/nullable.md new file mode 100644 index 00000000..7e581620 --- /dev/null +++ b/graph/nullable.md @@ -0,0 +1,311 @@ +# Nullable Properties + +A nullable property means *only* that the property may have `null` as a value; the "nullability" of a property does not say anything about how a value is set into a property. +For example, a non-nullable property is *not* required to create a new instance of an entity. +It only means that the property will have a value when it is retrieved. +In the case that no value is provided when the entity is created, this means that the service will create one; this value can be specified with the `DefaultValue` attribute, but if the value is contextual and determine at request time, then the property can both be non-nullable *and* have no `DefaultValue` specified. +Below are some examples of nullable and non-nullable properties. + +## CSDL + +```xml + +... + + + + + + + + + + ... + +``` + +## HTTP Requests + +### {1} Create a servicePrincipal with no properties + +```HTTP +POST /servicePrincipals + +400 Bad Request +{ + "error": { + "code": "badRequest", + "message": "The 'appId' property is required to create a servicePrincipal." + } +} +``` + +### {2} Create a servicePrincipal without a display name + +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001" +} + +201 Created +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "some application name", + "foo": "testval", + "bar": "differentvalue", + ... +} +``` +Notes: +1. `displayName` was given a value by the service even though no value was provided by the client +2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL +3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL + +### {3} Update the display name of a service principal to null + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "displayName": null +} + +400 Bad Request +{ + "error": { + "code": "badRequest", + "message": "null is not a valid value for the property 'displayName'; 'displayName' is not a nullable property." + } +} +``` +Notes: +1. `displayName` cannot be set to `null` because it has be marked with `Nullable="false"` in the CSDL. + +### {4} Update the display name of a service principal + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "displayName": "a non-generated display name" +} + +200 OK +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a non-generated display name", + "foo": "testval", + "bar": "differentvalue", + ... +} +``` +Notes: +1. `displayName` can be set to any value other than `null` +2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. + +### {5} Update the foo property of a service principal to null + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "foo": null +} + +200 OK +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a non-generated display name", + "foo": null, + "bar": "differentvalue", + ... +} +``` +Notes: +1. `foo` can be set to `null` because it has be marked with `Nullable="true"` in the CSDL. +2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. + +### {6} Update the foo property of a service principal to a non-default value + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "foo": "something other than testval" +} + +200 OK +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a non-generated display name", + "foo": "something other than testval", + "bar": "differentvalue", + ... +} +``` +Notes: +1. `foo` can be set to `something other than testval` +2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. + +### {7} Update the bar property of a service principal to null + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "bar": null +} + +400 Bad Request +{ + "error": { + "code": "badRequest", + "message": "null is not a valid value for the property 'bar'; 'bar' is not a nullable property." + } +} +``` +Notes: +1. `bar` cannot be set to `null` because it has be marked with `Nullable="false"` in the CSDL. + +### {8} Update the bar property of a service principal to a non-default value + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "bar": "a new bar" +} + +200 OK +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a non-generated display name", + "foo": "something other than testval", + "bar": "a new bar", + ... +} +``` +Notes: +1. `bar` can be set to `a new bar` +2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. + +### {9} Create a service principal while customizing the display name +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a different name" +} + +201 Created +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a different name", + "foo": "testval", + "bar": "differentvalue", + ... +} +``` +Notes: +1. `displayName` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. +2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL +3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL + +### {10} Create a service principal with a null display name +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": null +} + +400 Bad Request +{ + "error": { + "code": "badRequest", + "message": "null is not a valid value for the property 'displayName'; 'displayName' is not a nullable property." + } +} +``` +Notes: +1. `displayName` isn't required to create a new `servicePrincipal`, but it *can* be provided; it *cannot* be provided as `null` because the property was marked with `Nullable="false"` + +### {11} Create a service principal with a value for the foo property +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "foo": "a foo value on creation" +} + +201 Created +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "some application name", + "foo": "a foo value on creation", + "bar": "differentvalue", + ... +} +``` +Notes: +1. `displayName` was given a value by the service even though no value was provided by the client +2. `foo` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. +3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL + +### {12} Create a service principal with null for the foo property +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "foo": null +} + +201 Created +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "some application name", + "foo": null, + "bar": "differentvalue", + ... +} +``` +Notes: +1. `displayName` was given a value by the service even though no value was provided by the client +2. `foo` isn't required to create a new `servicePrincipal`, but it *can* be provided; because the property has `Nullable="true"`, a `null` value can be provided for it. +3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL + +### {13} Create a service principal with a value for the bar property +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "bar": "running out of ideas for value names" +} + +201 Created +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "some application name", + "foo": "testval", + "bar": "running out of ideas for value names", + ... +} +``` +Notes: +1. `displayName` was given a value by the service even though no value was provided by the client +2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL +3. `bar` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. + +### {14} Create a service principal with null for the bar property +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "bar": null +} + +400 Bad Request +{ + "error": { + "code": "badRequest", + "message": "null is not a valid value for the property 'bar'; 'bar' is not a nullable property." + } +} +``` +Notes: +1. `bar` isn't required to create a new `servicePrincipal`, but it *can* be provided; it *cannot* be provided as `null` because the property was marked with `Nullable="false"` From 7239729087d91b5c36df8f340ab0181f22a8b276 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:39:12 -0700 Subject: [PATCH 603/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 7dd2a35f..f1894423 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -150,26 +150,6 @@ In Microsoft Graph, a top-level API category might represent one of the followin Effectively, top-level categories define a perimeter for the API surface; thus, a new category creation requires additional rigor and governance approval. -### Query support - -Microsoft Graph APIs should support basic query options in conformance with OData specifications and [Microsoft REST API Guidelines for error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses). - -|Requirements | -|----------------------------------------------------------------------------------------------------| -| :heavy_check_mark: **MUST** support `$select on resource` to enable properties projection. | -| :ballot_box_with_check: **SHOULD** support `/entityTypeCollection/{id}?$expand=navProp1` option for navigation properties of entities. | -| :ballot_box_with_check: **SHOULD** support `$filter` with `eq` and `ne` operations on properties of entity collections. | -| :heavy_check_mark: **MUST** support pagination of collections (of entity types or complex types) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | -| :ballot_box_with_check: **MAY** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using `$skiptoken`. | -| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip`. | -| :ballot_box_with_check: **SHOULD** support `$count` for collections. | -| :ballot_box_with_check: **SHOULD** support sorting with `$orderby` both ascending and descending on properties of the entities. | - -The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or processing the request. One way to avoid this is to use the POST verb instead of GET with the `$query` segment, and pass the query options part of the URL in the request body as described in the chapter -[OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). - -Another way to avoid this is to use JSON batch as described in the [Microsoft Graph batching documentation](https://docs.microsoft.com/graph/json-batching#bypassing-url-length-limitations-with-batching). - ### Resource modeling patterns You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type does not. In Microsoft Graph, this key property is called `id` for server-created key values. If there is a natural name for the key property, then the workload can use that. @@ -257,8 +237,9 @@ Microsoft Graph APIs should support basic query options in conformance with ODat | :heavy_check_mark: **MUST** support `$select on resource` to enable properties projection. | | :ballot_box_with_check: **SHOULD** support `/entityTypeCollection/{id}?$expand=navProp1` option for navigation properties of entities. | | :ballot_box_with_check: **SHOULD** support `$filter` with `eq` and `ne` operations on properties of entity collections. | -| :heavy_check_mark: **MUST** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | -| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip` (or `$skipToken`). | +| :heavy_check_mark: **MUST** support pagination of collections (of entity types or complex types) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | +| :ballot_box_with_check: **MAY** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using `$skiptoken`. | +| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip`. | | :ballot_box_with_check: **SHOULD** support `$count` for collections. | | :ballot_box_with_check: **SHOULD** support sorting with `$orderby` both ascending and descending on properties of the entities. | From f7e123a5cc6f836b260e39610a4c8c3b5d177df3 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:03:47 -0400 Subject: [PATCH 604/729] Update Guidelines.md Co-authored-by: Peter Richards <15617285+PetRich-MSFT@users.noreply.github.com> --- Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guidelines.md b/Guidelines.md index d54705b0..fbbdfa5a 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -983,7 +983,7 @@ The `@nextLink` MAY be populated using either server-driven paging or client-dri #### 9.8.2. Server-driven paging The server MAY provide server-driven paging by populating the continuation token with a `$skiptoken` query parameter. -The `$skiptoken` value is opague for clients and its structure should not be assumed. +The `$skiptoken` value is opaque for clients and its structure should not be assumed. `$skiptoken` values SHOULD expire after some period of time decided by the server. Example: From e7823721b4fe67ede8839d4117dac0486b162596 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Tue, 24 Oct 2023 23:44:51 -0700 Subject: [PATCH 605/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index f1894423..f5206b77 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -14,6 +14,7 @@ Table of contents - [Behavior modeling](#behavior-modeling) - [Error handling](#error-handling) - [Enums](#enums) + - [External Standards](#external-standards) - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) - [Versioning and deprecation](#versioning-and-deprecation) - [Recommended API design patterns](#recommended-api-design-patterns) @@ -328,6 +329,17 @@ For a complete mapping of error codes to HTTP statuses, see +## External standards + +For ease of client use and interoperatibility, some APIs should implement a standard that is defined external to Microsoft Graph and OData. +Workloads should follow these standards exactly, even if they conflict with the OData standard and/or the Microsoft Graph guidelines. +Workloads must define these standards in their CSDL model if they do not conflict with the OData standard. +Standards that *do* conflict with the OData standard may be defined in the CSDL in one of two ways: +1. Using `Edm.Untyped` only and support for the external standard will come directly from the service implementation; OR +2. Adding CSDL elements to model the external standard using `Edm.String` for `EnumType`s that conflict with the OData standard and `Edm.Untyped` wherever any other conflict with the OData standard occurs + +The benefit of the second approach is that strongly-typed models have SDK support for clients and also have significant tooling support for both the workload and clients. Note that it is backwards compatible for a workload to migrate from the second approach to the first approach in case the external standard is *initially* compliant with the OData standard and *later* conflicts with the OData standard. + ## API contract and non-backward compatible changes The Microsoft Graph definition of breaking changes is based on the From 5bedf7c0e2d5b3280c2928deeda83e029b0ca1f5 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Tue, 24 Oct 2023 23:54:01 -0700 Subject: [PATCH 606/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index f5206b77..96ca46e5 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -338,7 +338,9 @@ Standards that *do* conflict with the OData standard may be defined in the CSDL 1. Using `Edm.Untyped` only and support for the external standard will come directly from the service implementation; OR 2. Adding CSDL elements to model the external standard using `Edm.String` for `EnumType`s that conflict with the OData standard and `Edm.Untyped` wherever any other conflict with the OData standard occurs -The benefit of the second approach is that strongly-typed models have SDK support for clients and also have significant tooling support for both the workload and clients. Note that it is backwards compatible for a workload to migrate from the second approach to the first approach in case the external standard is *initially* compliant with the OData standard and *later* conflicts with the OData standard. +In either case, any use of `Edm.String` instead of an `EnumType` or any use of `Edm.Untyped` must be documented with references to the standard that the client is expected to follow. +The benefit of the second approach is that strongly-typed models have SDK support for clients and also have significant tooling support for both the workload and clients. +Note that it is backwards compatible for a workload to migrate from the second approach to the first approach in case the external standard is *initially* compliant with the OData standard and *later* conflicts with the OData standard. ## API contract and non-backward compatible changes From d156bc74f1fc3a57bc796cd53ae98e109f56da6f Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 25 Oct 2023 00:11:52 -0700 Subject: [PATCH 607/729] Update navigation-property.md --- graph/patterns/navigation-property.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 3b06d5e1..6dd3a756 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -35,12 +35,15 @@ These relationships can be described in CSDL as follows: ## Issues and Considerations ------------------------- -In the current Microsoft Graph implementation, there are some limitations on the use of navigation properties that cross between backend services. These limitations are being eliminated over time, but it will be necessary to ensure support for any particular scenario. [Limitations of the current implementation](https://dev.azure.com/msazure/One/_wiki/wikis/Microsoft%20Graph%20Partners/354352/Cross-workload-navigations?anchor=supported-scenarios) are documented internally. +In the current Microsoft Graph implementation, there are scenarios which use navigation properties that cross backend services that have automatic support; there are also some limitations for other scenarios. These limitations are being eliminated over time, but it will be necessary to ensure support for any particular scenario. [Automatic support and limitations of the current implementation](https://dev.azure.com/msazure/One/_wiki/wikis/Microsoft%20Graph%20Partners/354352/Cross-workload-navigations?anchor=supported-scenarios) are documented internally.   Navigation properties defined within an entity are not returned by default when retreiving the representation of an entity unless explicity desired by a service. The API can consumer can use the `expand` query parameterm, where supported, to retreive both the source and the target entity of the relationship in a single request. Implementing support for accessing the "$ref" of a navigation property allows a caller to return just the URL of related resource. e.g. `/user/23/manager/$ref`. This is useful when a client wishes to identify the related resource but doesn't need all of its properties. +The strongly-typed nature of navigation properties is valuable for backend services and for client applications. +Strong typing allows documentation and visualizations to be automatically generated, it allows SDK generation, and it allows some automated client code generation; it also prevents the need to store duplicate data on the service side and as a result has improved data consistency across APIs since the duplicate data does not need to be regularly refreshed. + ## When to Use this Pattern ------------------------ From 50ee71f8a6bb02283148ec97aefb7b726aec6c1b Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 25 Oct 2023 00:14:05 -0700 Subject: [PATCH 608/729] Update navigation-property.md --- graph/patterns/navigation-property.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 6dd3a756..338e5210 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -42,7 +42,7 @@ Navigation properties defined within an entity are not returned by default when Implementing support for accessing the "$ref" of a navigation property allows a caller to return just the URL of related resource. e.g. `/user/23/manager/$ref`. This is useful when a client wishes to identify the related resource but doesn't need all of its properties. The strongly-typed nature of navigation properties is valuable for backend services and for client applications. -Strong typing allows documentation and visualizations to be automatically generated, it allows SDK generation, and it allows some automated client code generation; it also prevents the need to store duplicate data on the service side and as a result has improved data consistency across APIs since the duplicate data does not need to be regularly refreshed. +Strong typing allows some documentation and visualizations to be automatically generated, it allows SDK generation, and it allows some automated client code generation; it also prevents the need to store duplicate data on the service side and as a result has improved data consistency across APIs since the duplicate data does not need to be regularly refreshed. ## When to Use this Pattern ------------------------ From 17e9b009ea4065f7a13186df1df8260bc5310fa3 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 25 Oct 2023 13:15:39 -0700 Subject: [PATCH 609/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 96ca46e5..a1879938 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -338,7 +338,7 @@ Standards that *do* conflict with the OData standard may be defined in the CSDL 1. Using `Edm.Untyped` only and support for the external standard will come directly from the service implementation; OR 2. Adding CSDL elements to model the external standard using `Edm.String` for `EnumType`s that conflict with the OData standard and `Edm.Untyped` wherever any other conflict with the OData standard occurs -In either case, any use of `Edm.String` instead of an `EnumType` or any use of `Edm.Untyped` must be documented with references to the standard that the client is expected to follow. +In either case, any use of `Edm.String` instead of an `EnumType` or any use of `Edm.Untyped` must provide a [description annotation](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.xml#L105) to document references to the standard that the client is expected to follow. The benefit of the second approach is that strongly-typed models have SDK support for clients and also have significant tooling support for both the workload and clients. Note that it is backwards compatible for a workload to migrate from the second approach to the first approach in case the external standard is *initially* compliant with the OData standard and *later* conflicts with the OData standard. From c00f2a26483951e3f6f12566d122ad36c234636d Mon Sep 17 00:00:00 2001 From: Paul Mandel <142252044+artificial-paul@users.noreply.github.com> Date: Sun, 29 Oct 2023 18:40:24 -0700 Subject: [PATCH 610/729] Update Guidelines.md Adding missing newline --- azure/Guidelines.md | 1 + 1 file changed, 1 insertion(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ae15b2b3..67c37839 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -41,6 +41,7 @@ These guidelines apply to Azure service teams implementing _data plane_ APIs. Th - Sustainable & versionable via clear API contracts with 2 requirements: 1. Customer workloads must never break due to a service change 2. Customers can adopt a version without requiring code changes + Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. Please read the [Considerations for Service Design](./ConsiderationsForServiceDesign.md) for an introduction to the topic of API design for Azure services. *For an existing GA'd service, don't change/break its existing API; instead, leverage these concepts for future APIs while prioritizing consistency within your existing service.* *Note: If you are creating a management plane (ARM) API, please refer to the [Azure Resource Manager Resource Provider Contract](https://github.com/Azure/azure-resource-manager-rpc).* From 956726c0b318f7b3388ea1af2871b9d1f09ae720 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Mon, 6 Nov 2023 15:58:24 -0800 Subject: [PATCH 611/729] Remove extraneous backticks around filter (#502) --- azure/Guidelines.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ae15b2b3..2e15dfec 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -589,23 +589,23 @@ Parameter name | Type | Description :no_entry: **DO NOT** prefix any of these query parameter names with "$" (the convention in the [OData standard](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_QueryingCollections)). -#### `filter` +#### filter -:heavy_check_mark: **YOU MAY** support `filter`ing of the results of a list operation with the `filter` query parameter. +:heavy_check_mark: **YOU MAY** support filtering of the results of a list operation with the `filter` query parameter. -The value of the `filter` option is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response. +The value of the `filter` query parameter is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response. -:white_check_mark: **DO** omit all resources from the collection for which the `filter` expression evaluates to false or to null, or references properties that are unavailable due to permissions. +:white_check_mark: **DO** omit all resources from the collection for which the filter expression evaluates to false or to null, or references properties that are unavailable due to permissions. Example: return all Products whose Price is less than $10.00 ```text -GET https://api.contoso.com/products?`filter`=price lt 10.00 +GET https://api.contoso.com/products?filter=price lt 10.00 ``` -##### `filter` operators +##### filter operators -:heavy_check_mark: **YOU MAY** support the following operators in `filter` expressions: +:heavy_check_mark: **YOU MAY** support the following operators in filter expressions: Operator | Description | Example -------------------- | --------------------- | ----------------------------------------------------- @@ -623,9 +623,9 @@ not | Logical negation | not price le 3.5 **Grouping Operators** | | ( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 -:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#handling-errors) section if a client includes an operator in a `filter` expression that is not supported by the operation. +:white_check_mark: **DO** respond with an error message as defined in the [Handling Errors](#handling-errors) section if a client includes an operator in a filter expression that is not supported by the operation. -:white_check_mark: **DO** use the following operator precedence for supported operators when evaluating `filter` expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: +:white_check_mark: **DO** use the following operator precedence for supported operators when evaluating filter expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right: | Group | Operator | Description | ----------------|----------|------------ @@ -640,7 +640,7 @@ not | Logical negation | not price le 3.5 | Conditional AND | and | Logical And | | Conditional OR | or | Logical Or | -:heavy_check_mark: **YOU MAY** support orderby and `filter` functions such as concat and contains. For more information, see [odata Canonical Functions](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360979). +:heavy_check_mark: **YOU MAY** support orderby and filter functions such as concat and contains. For more information, see [odata Canonical Functions](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360979). ##### Operator examples The following examples illustrate the use and semantics of each of the logical operators. @@ -648,31 +648,31 @@ The following examples illustrate the use and semantics of each of the logical o Example: all products with a name equal to 'Milk' ```text -GET https://api.contoso.com/products?`filter`=name eq 'Milk' +GET https://api.contoso.com/products?filter=name eq 'Milk' ``` Example: all products with a name not equal to 'Milk' ```text -GET https://api.contoso.com/products?`filter`=name ne 'Milk' +GET https://api.contoso.com/products?filter=name ne 'Milk' ``` Example: all products with the name 'Milk' that also have a price less than 2.55: ```text -GET https://api.contoso.com/products?`filter`=name eq 'Milk' and price lt 2.55 +GET https://api.contoso.com/products?filter=name eq 'Milk' and price lt 2.55 ``` Example: all products that either have the name 'Milk' or have a price less than 2.55: ```text -GET https://api.contoso.com/products?`filter`=name eq 'Milk' or price lt 2.55 +GET https://api.contoso.com/products?filter=name eq 'Milk' or price lt 2.55 ``` Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: ```text -GET https://api.contoso.com/products?`filter`=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 +GET https://api.contoso.com/products?filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 ``` #### orderby @@ -705,15 +705,15 @@ For example, to return all people sorted by name in descending order and a secon GET https://api.contoso.com/people?orderby=name desc,hireDate ``` -Sorting MUST compose with `filter`ing such that: +Sorting MUST compose with filtering such that: ```text -GET https://api.contoso.com/people?`filter`=name eq 'david'&orderby=hireDate +GET https://api.contoso.com/people?filter=name eq 'david'&orderby=hireDate ``` will return all people whose name is David sorted in ascending order by hireDate. ##### Considerations for sorting with pagination -:white_check_mark: **DO** use the same `filter`ing options and sort order for all pages of a paginated list operation response. +:white_check_mark: **DO** use the same filtering options and sort order for all pages of a paginated list operation response. ##### skip :white_check_mark: **DO** define the `skip` parameter as an integer with a default and minimum value of 0. From 62b01532287b68d9f59f56248255e9413e623c36 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:23:49 -0800 Subject: [PATCH 612/729] Create graph-structure.md --- graph/patterns/graph-structure.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 graph/patterns/graph-structure.md diff --git a/graph/patterns/graph-structure.md b/graph/patterns/graph-structure.md new file mode 100644 index 00000000..26336e73 --- /dev/null +++ b/graph/patterns/graph-structure.md @@ -0,0 +1,30 @@ +# Pattern name + +Microsoft Graph API Design Pattern + +*Provide a short description of the pattern.* + + +## Problem + +*Describe the business context relevant for the pattern.* + +*Provide a short description of the problem.* + +## Solution + +*Describe how to implement the solution to solve the problem.* + +*Describe related patterns.* + +## When to use this pattern + +*Describe when and why the solution is applicable and when it might not be.* + +## Issues and considerations + +*Describe tradeoffs of the solution.* + +## Example + +*Provide a short example from real life.* From 22b96868badf81f319cb2523a5c2453be395ec1f Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:24:09 -0800 Subject: [PATCH 613/729] Delete graph/patterns/graph-structure.md --- graph/patterns/graph-structure.md | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 graph/patterns/graph-structure.md diff --git a/graph/patterns/graph-structure.md b/graph/patterns/graph-structure.md deleted file mode 100644 index 26336e73..00000000 --- a/graph/patterns/graph-structure.md +++ /dev/null @@ -1,30 +0,0 @@ -# Pattern name - -Microsoft Graph API Design Pattern - -*Provide a short description of the pattern.* - - -## Problem - -*Describe the business context relevant for the pattern.* - -*Provide a short description of the problem.* - -## Solution - -*Describe how to implement the solution to solve the problem.* - -*Describe related patterns.* - -## When to use this pattern - -*Describe when and why the solution is applicable and when it might not be.* - -## Issues and considerations - -*Describe tradeoffs of the solution.* - -## Example - -*Provide a short example from real life.* From 90e9ca6ab11c84877452b16c57c0d2ec723b015d Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 19 Nov 2023 17:18:58 -0600 Subject: [PATCH 614/729] Add http-query-names-casing rule --- azure/Guidelines.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 2e15dfec..eab76b73 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -168,6 +168,11 @@ DELETE | Remove the resource | `204-No Content`\; avoid `404-Not Found` :white_check_mark: **DO** support caching and optimistic concurrency by honoring the the `If-Match`, `If-None-Match`, if-modified-since, and if-unmodified-since request headers and by returning the ETag and last-modified response headers #### HTTP Query Parameters and Header Values + +:white_check_mark: **DO** use camel case for query parameter names. + +Note: Certain legacy query parameter names use kebab-casing and are allowed only for backwards compatibility. + Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values. :white_check_mark: **DO** validate all query parameter and request header values and fail the operation with `400-Bad Request` if any value fails validation. Return an error response as described in the [Handling Errors](#handling-errors) section indicating what is wrong so customer can diagnose the issue and fix it themselves. From 8c2578542b050d0fd18256846a30937c9652d3e2 Mon Sep 17 00:00:00 2001 From: Troy Connor Date: Thu, 20 Jul 2023 12:09:22 -0400 Subject: [PATCH 615/729] fix link for breaking change documentation vanity link Signed-off-by: Troy Connor --- azure/Guidelines.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index eab76b73..c804afec 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -13,7 +13,7 @@ Please ensure that you add an anchor tag to any new guidelines that you add and
    Expand change history - + | Date | Notes | | ----------- | -------------------------------------------------------------- | | 2023-May-12 | Explain service response for missing/unsupported `api-version` | @@ -749,7 +749,7 @@ Azure services need to change over time. However, when changing a service, there 1. Already-running customer workloads must not break due to a service change 2. Customers can adopt a new service version without requiring any code changes (Of course, the customer must modify code to leverage any new service features.) -*NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy/) has tables (section 5) describing what kinds of changes are considered breaking. Breaking changes are allowable (due to security/compliance/etc.) if approved by the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) but only following ample communication to customers and a lengthy deprecation period.* +*NOTE: the [Azure Breaking Change Policy](http://aka.ms/AzBreakingChangesPolicy) has tables (section 5) describing what kinds of changes are considered breaking. Breaking changes are allowable (due to security/compliance/etc.) if approved by the [Azure Breaking Change Reviewers](mailto:azbreakchangereview@microsoft.com) but only following ample communication to customers and a lengthy deprecation period.* :white_check_mark: **DO** review any API changes with the Azure API Stewardship Board @@ -809,7 +809,7 @@ When the [API Versioning](#api-versioning) guidance above cannot be followed and The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation and use a newer API version or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. The string is _not_ part of an API contract (except for the semi-colon delimiters) and may be changed/improved at any time without incurring a breaking change. -:white_check_mark: **DO** include the `azure-deprecating` header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. +:white_check_mark: **DO** include the `azure-deprecating` header in the operation's response _only if_ the operation will stop working in the future and the client _must take_ action in order for it to keep working. > NOTE: We do not want to scare customers with this header. :white_check_mark: **DO** make the header's value a semicolon-delimited string indicating a set of deprecations where each one indicates what is deprecating, when it is deprecating, and a URL to more information. From f2c9d839c3a548017dcc1e8d6452e4d118d6c2a2 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Tue, 30 Aug 2022 09:05:20 -0700 Subject: [PATCH 616/729] small updates for versioning --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index c804afec..72053214 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -297,7 +297,7 @@ Because of this, required fields can only be introduced in the 1st version of a When using this method | if this condition happens | use this response code ---------------------- | ------------------------- | ---------------------- -PATCH/PUT | Any JSON field name/value not known/valid | `400-Bad Request` +PATCH/PUT | Any JSON field name/value not known/known to version/valid | `400-Bad Request` PATCH/PUT | Any Read field passed (client can't set Read fields) | `400-Bad Request` | **If the resource does not exist** | PATCH/PUT | Any mandatory Create/Update field missing | `400-Bad Request` From e1147515c06cf5f0368acd95b7f223791a40c2cc Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Tue, 30 Aug 2022 10:07:45 -0700 Subject: [PATCH 617/729] Update azure/Guidelines.md Co-authored-by: Jeffrey Richter --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 72053214..6de36585 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -297,7 +297,7 @@ Because of this, required fields can only be introduced in the 1st version of a When using this method | if this condition happens | use this response code ---------------------- | ------------------------- | ---------------------- -PATCH/PUT | Any JSON field name/value not known/known to version/valid | `400-Bad Request` +PATCH/PUT | Any JSON field name/value not known/valid to the api-version | `400-Bad Request` PATCH/PUT | Any Read field passed (client can't set Read fields) | `400-Bad Request` | **If the resource does not exist** | PATCH/PUT | Any mandatory Create/Update field missing | `400-Bad Request` From eb46156be147d67811c3ff66275cf8948eb2e69c Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Tue, 18 Apr 2023 10:41:31 -0700 Subject: [PATCH 618/729] Update Etag guideline for collection values --- azure/Guidelines.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 6de36585..a299a071 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -543,16 +543,16 @@ NOTE: It is a breaking change to add paging in the future ```json { "value": [ - { "id": "Item 01", "etag": "0xabc", "price": 99.95, "sizes": null }, + { "id": "Item 01", "etag": "\"abc\"", "price": 99.95, "sizes": null }, { … }, { … }, - { "id": "Item 99", "etag": "0xdef", "price": 59.99, "sizes": null } + { "id": "Item 99", "etag": "\"def\"", "price": 59.99, "sizes": null } ], "nextLink": "{opaqueUrl}" } ``` -:white_check_mark: **DO** include the _id_ field and _etag_ field (if supported) for each item as this allows the customer to modify the item in a future operation. +:white_check_mark: **DO** include the _id_ field and _etag_ field (if supported) for each item as this allows the customer to modify the item in a future operation. Note that the tag field _must_ have escaped-quotes embedded within it; for example "\"abc\"" or W/"\"abc\"". :white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). From e18b48c784d1ff214c1aee9fd5ca223a13492865 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Tue, 18 Apr 2023 10:51:39 -0700 Subject: [PATCH 619/729] Update azure/Guidelines.md --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index a299a071..8d6f6bb4 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -552,7 +552,7 @@ NOTE: It is a breaking change to add paging in the future } ``` -:white_check_mark: **DO** include the _id_ field and _etag_ field (if supported) for each item as this allows the customer to modify the item in a future operation. Note that the tag field _must_ have escaped-quotes embedded within it; for example "\"abc\"" or W/"\"abc\"". +:white_check_mark: **DO** include the _id_ field and _etag_ field (if supported) for each item as this allows the customer to modify the item in a future operation. Note that the etag field _must_ have escaped quotes embedded within it; for example, "\"abc\"" or W/"\"abc\"". :white_check_mark: **DO** clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection). From 83568c5dd9b89a4a4f3b2c99b13af31f02434737 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 21 Apr 2023 09:20:56 -0700 Subject: [PATCH 620/729] Update Repeatability headers section --- azure/Guidelines.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 8d6f6bb4..ddee0bd0 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -17,6 +17,7 @@ Please ensure that you add an anchor tag to any new guidelines that you add and | Date | Notes | | ----------- | -------------------------------------------------------------- | | 2023-May-12 | Explain service response for missing/unsupported `api-version` | +| 2023-Apr-21 | Update/clarify guidelines on POST method repeatability | | 2023-Apr-07 | Update/clarify guidelines on polymorphism | | 2022-Sep-07 | Updated URL guidelines for DNS Done Right | | 2022-Jul-15 | Update guidance on long-running operations | @@ -836,13 +837,13 @@ For example: ### Repeatability of requests -The ability to retry failed requests for which a client never received a response greatly simplifies the ability to write resilient distributed applications. While HTTP designates some methods as safe and/or idempotent (and thus retryable), being able to retry other operations such as create-using-POST-to-collection is desirable. - -:ballot_box_with_check: **YOU SHOULD** support repeatable requests according as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html). +Fault tolerant applications require that clients retry requests for which they never got a response, and services must handle these retried requests idempotently. In Azure, all HTTP operations are naturally idempotent except for POST used to create a resource and [POST when used to invoke an action]( +https://github.com/microsoft/api-guidelines/blob/d81151d990b437b34dcf7d05d9504b754eb4f456/azure/Guidelines.md#performing-an-action). +:ballot_box_with_check: **YOU SHOULD** support repeatable requests as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html) for POST operations to make them retriable. - The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. -- A service advertises support for repeatability requests by adding the `Repeatability-First-Sent` and `Repeatability-Request-ID` to the set of headers for a given operation. -- When understood, all endpoints co-located behind a DNS name **MUST** understand the header. This means that a service **MUST NOT** ignore the presence of a header for any endpoints behind the DNS name, but rather fail the request containing a `Repeatability-Request-ID` header if that particular endpoint lacks support for repeatable requests. Such partial support **SHOULD** be avoided due to the confusion it causes for clients. +- Document the POST operation's support for the `Repeatability-First-Sent`, `Repeatability-Request-ID`, and `Repeatability-Result` headers in the API contract and documentation. +- Any operation that does not support repeatability headers should return a 501 (Not Implemented) response for any request that contains valid repeatability request headers. ### Long-Running Operations & Jobs From 32bbde2a2f5456db98c4b71f4c5152a74bd0b282 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Tue, 5 Dec 2023 21:03:52 -0600 Subject: [PATCH 621/729] Update azure/Guidelines.md --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ddee0bd0..dee45afa 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -838,7 +838,7 @@ For example: ### Repeatability of requests Fault tolerant applications require that clients retry requests for which they never got a response, and services must handle these retried requests idempotently. In Azure, all HTTP operations are naturally idempotent except for POST used to create a resource and [POST when used to invoke an action]( -https://github.com/microsoft/api-guidelines/blob/d81151d990b437b34dcf7d05d9504b754eb4f456/azure/Guidelines.md#performing-an-action). +https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#performing-an-action). :ballot_box_with_check: **YOU SHOULD** support repeatable requests as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html) for POST operations to make them retriable. - The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. From 5c643280486393b1b00cbcbab3652c5fccd20fe9 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:48:11 -0800 Subject: [PATCH 622/729] Update graph/GuidelinesGraph.md Co-authored-by: Darrel --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index f4946df4..b1978f69 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -115,7 +115,7 @@ Following is a short summary of the most often used conventions. | :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem or userId on auditActor
    - **Wrong:** webUrl on contact when it's the companyWebUrl | | :ballot_box_with_check: **SHOULD** prefix Boolean properties with `is`, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong:** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom (subjectively more natural than the following examples)
    - **Wrong:** isNewTimeProposalsAllowed or isInvitesFromAllowed (subjectively more awkward that the preceding examples) | | :no_entry: **MUST NOT** use collection, response, or request suffixes. | - **Right:** addresses
    - **Wrong:** addressCollection | -| :no_entry: **MUST NOT** contain product names. | - **Right:** chatMessages
    - **Wrong:** teamsMessages
    - **NOTE** there is an exception for resources that *only* exist under the `/admin` root segment | +| :no_entry: **MUST NOT** contain product names. | - **Right:** chatMessages
    - **Wrong:** teamsMessages
    - **NOTE** there is an exception for resources that *only* exist under the `/admin` root segment and the `/users/{userId}/settings` path. | ### Uniform Resource Locators (URLs) From d7daa0bedf5f18fb23f0ef79b208d275b7b3b8b2 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 8 Dec 2023 09:12:12 -0800 Subject: [PATCH 623/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index b1978f69..9b88febb 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -97,7 +97,7 @@ Following is a short summary of the most often used conventions. | Requirements | Example | | ------------------------------------------------------------------------|-------------------------------| | :no_entry: **MUST NOT** use redundant words in names. | - **Right:** /places/{id}/**displayName** or /phones/{id}/**number**
    - **Wrong:** /places/{id}/**placeName** or /phones/{id}/**phoneNumber** | -| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong:** teamsChat | +| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong:** teamsChat
    - **NOTE** there is an exception for resources that *only* exist under the `/admin` root segment and the `/users/{userId}/settings` path. | | :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong:** msodsUrl or dlp | | :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong:** addresses | | :heavy_check_mark: **MUST** use plural nouns for collections (for listing type or collection properties). | - **Right:** addresses
    - **Wrong:** address | @@ -115,7 +115,6 @@ Following is a short summary of the most often used conventions. | :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem or userId on auditActor
    - **Wrong:** webUrl on contact when it's the companyWebUrl | | :ballot_box_with_check: **SHOULD** prefix Boolean properties with `is`, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong:** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom (subjectively more natural than the following examples)
    - **Wrong:** isNewTimeProposalsAllowed or isInvitesFromAllowed (subjectively more awkward that the preceding examples) | | :no_entry: **MUST NOT** use collection, response, or request suffixes. | - **Right:** addresses
    - **Wrong:** addressCollection | -| :no_entry: **MUST NOT** contain product names. | - **Right:** chatMessages
    - **Wrong:** teamsMessages
    - **NOTE** there is an exception for resources that *only* exist under the `/admin` root segment and the `/users/{userId}/settings` path. | ### Uniform Resource Locators (URLs) From 6ca5135c33a85b4ea00c60ef0a6f0e47c242239b Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 8 Dec 2023 09:12:55 -0800 Subject: [PATCH 624/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 9b88febb..54cc44ef 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -97,7 +97,7 @@ Following is a short summary of the most often used conventions. | Requirements | Example | | ------------------------------------------------------------------------|-------------------------------| | :no_entry: **MUST NOT** use redundant words in names. | - **Right:** /places/{id}/**displayName** or /phones/{id}/**number**
    - **Wrong:** /places/{id}/**placeName** or /phones/{id}/**phoneNumber** | -| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong:** teamsChat
    - **NOTE** there is an exception for resources that *only* exist under the `/admin` root segment and the `/users/{userId}/settings` path. | +| :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong:** teamsChat
    - **NOTE:** there is an exception for resources that *only* exist under the `/admin` root segment and the `/users/{userId}/settings` path. | | :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong:** msodsUrl or dlp | | :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong:** addresses | | :heavy_check_mark: **MUST** use plural nouns for collections (for listing type or collection properties). | - **Right:** addresses
    - **Wrong:** address | From 62a5b44d0d036627ba67d1f1549e04129cb558a2 Mon Sep 17 00:00:00 2001 From: "Dan Kershaw [MSFT]" Date: Mon, 18 Dec 2023 19:36:25 +0000 Subject: [PATCH 625/729] Updated based on feedback and triage with Olga. --- graph/patterns/default-properties.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph/patterns/default-properties.md b/graph/patterns/default-properties.md index 0736dc4a..879612f7 100644 --- a/graph/patterns/default-properties.md +++ b/graph/patterns/default-properties.md @@ -2,17 +2,17 @@ Microsoft Graph API Design Pattern -*The default properties pattern allows API producers to omit specific properties from the response unless they are explicitly requested using `$select`. The pattern also uses an instance annotation to inform callers that other properties are also available. The same annotation is also use to encourage callers to use `$select`.* +*The default properties pattern allows API producers to omit specific properties from the response unless they are explicitly requested using `$select`.* ## Problem -API producers want to control the set of properties that their entities return by default, when the incoming request does not specify a `$select`. This can be desirable when an entity type has many properties or an API producer needs to add properties that are computationally expensive to return by default. +API designers want to control the set of properties that their entities return by default, when the incoming request does not specify a `$select`. This can be desirable when an entity type has many properties or an API producer needs to add properties that are computationally expensive to return by default. ## Solution -For incoming requests targeting an entity type where the caller does not specify a `$select`` clause, API producers **may** return a subset of the entity type's properties, omitting computationally expensive properties. +For incoming requests targeting an entity type where the caller does not specify a `$select` clause, API producers **may** return a subset of the entity type's properties, omitting computationally expensive properties. To get the non-default properties of an entity type, callers must explicitly request them using `$select`. -To get the non-default properties of an entity type, callers must explicitly request them using `$select`. +The pattern also uses an instance annotation to inform callers that other properties are also available. The same annotation is also used to encourage callers to use `$select`. ## When to use this pattern From 039b8cf10db6eafe0eed046782af92e19d990916 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 18 Dec 2023 18:05:52 -0500 Subject: [PATCH 626/729] deprecating REST --- Guidelines.md | 2482 +------------------------------------------------ README.md | 28 +- 2 files changed, 12 insertions(+), 2498 deletions(-) diff --git a/Guidelines.md b/Guidelines.md index fbbdfa5a..e1125f06 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -1,2486 +1,10 @@ > # NOTICE TO READERS +> This document has been deprecated and has been merged with the [Microsoft Graph REST API Guidelines](./graph/Guidelines.md). Please refer to the notes below for the latest guidance. > > ## **Guidance for Azure service teams** -> Azure service teams should use the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. +> Azure service teams should use the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information, see the [README](./azure/README.md) in the Azure folder. > > ## **Guidance for Microsoft Graph service teams** -> Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. +> Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. --- - -# Microsoft REST API Guidelines - -## Microsoft REST API Guidelines Working Group - -Name | Name | Name | ----------------------------- | -------------------------------------- | ---------------------------------------- -Dave Campbell (CTO C+E) | Rick Rashid (CTO ASG) | John Shewchuk (Technical Fellow, TED HQ) -Mark Russinovich (CTO Azure) | Steve Lucco (Technical Fellow, DevDiv) | Murali Krishnaprasad (Azure App Plat) -Rob Howard (ASG) | Peter Torr (OSG) | Chris Mullins (ASG) - -
    -Document editors: John Gossman (C+E), Chris Mullins (ASG), Gareth Jones (ASG), Rob Dolin (C+E), Mark Stafford (C+E)
    -
    - -# Microsoft REST API Guidelines - -## 1. Abstract -The Microsoft REST API Guidelines, as a design principle, encourages application developers to have resources accessible to them via a RESTful HTTP interface. -To provide the smoothest possible experience for developers on platforms following the Microsoft REST API Guidelines, REST APIs SHOULD follow consistent design guidelines to make using them easy and intuitive. - -This document establishes the guidelines Microsoft REST APIs SHOULD follow so RESTful interfaces are developed consistently. - -## 2. Table of contents - - -- [Microsoft REST API Guidelines](#microsoft-rest-api-guidelines) - - [Microsoft REST API Guidelines Working Group](#microsoft-rest-api-guidelines-working-group) -- [Microsoft REST API Guidelines](#microsoft-rest-api-guidelines-1) - - [1. Abstract](#1-abstract) - - [2. Table of contents](#2-table-of-contents) - - [3. Introduction](#3-introduction) - - [3.1. Recommended reading](#31-recommended-reading) - - [4. Interpreting the guidelines](#4-interpreting-the-guidelines) - - [4.1. Application of the guidelines](#41-application-of-the-guidelines) - - [4.2. Guidelines for existing services and versioning of services](#42-guidelines-for-existing-services-and-versioning-of-services) - - [4.3. Requirements language](#43-requirements-language) - - [4.4. License](#44-license) - - [5. Taxonomy](#5-taxonomy) - - [5.1. Errors](#51-errors) - - [5.2. Faults](#52-faults) - - [5.3. Latency](#53-latency) - - [5.4. Time to complete](#54-time-to-complete) - - [5.5. Long running API faults](#55-long-running-api-faults) - - [6. Client guidance](#6-client-guidance) - - [6.1. Ignore rule](#61-ignore-rule) - - [6.2. Variable order rule](#62-variable-order-rule) - - [6.3. Silent fail rule](#63-silent-fail-rule) - - [7. Consistency fundamentals](#7-consistency-fundamentals) - - [7.1. URL structure](#71-url-structure) - - [7.2. URL length](#72-url-length) - - [7.3. Canonical identifier](#73-canonical-identifier) - - [7.4. Supported methods](#74-supported-methods) - - [7.4.1. POST](#741-post) - - [7.4.2. PATCH](#742-patch) - - [7.4.3. Creating resources via PATCH (UPSERT semantics)](#743-creating-resources-via-patch-upsert-semantics) - - [7.4.4. Options and link headers](#744-options-and-link-headers) - - [7.5. Standard request headers](#75-standard-request-headers) - - [7.6. Standard response headers](#76-standard-response-headers) - - [7.7. Custom headers](#77-custom-headers) - - [7.8. Specifying headers as query parameters](#78-specifying-headers-as-query-parameters) - - [7.9. PII parameters](#79-pii-parameters) - - [7.10. Response formats](#710-response-formats) - - [7.10.1. Clients-specified response format](#7101-clients-specified-response-format) - - [7.10.2. Error condition responses](#7102-error-condition-responses) - - [ErrorResponse : Object](#errorresponse--object) - - [Error : Object](#error--object) - - [InnerError : Object](#innererror--object) - - [Examples](#examples) - - [7.11. HTTP Status Codes](#711-http-status-codes) - - [7.12. Client library optional](#712-client-library-optional) - - [8. CORS](#8-cors) - - [8.1. Client guidance](#81-client-guidance) - - [8.1.1. Avoiding preflight](#811-avoiding-preflight) - - [8.2. Service guidance](#82-service-guidance) - - [9. Collections](#9-collections) - - [9.1. Item keys](#91-item-keys) - - [9.2. Serialization](#92-serialization) - - [9.3. Collection URL patterns](#93-collection-url-patterns) - - [9.3.1. Nested collections and properties](#931-nested-collections-and-properties) - - [9.4. Big collections](#94-big-collections) - - [9.5. Changing collections](#95-changing-collections) - - [9.6. Sorting collections](#96-sorting-collections) - - [9.6.1. Interpreting a sorting expression](#961-interpreting-a-sorting-expression) - - [9.7. Filtering](#97-filtering) - - [9.7.1. Filter operations](#971-filter-operations) - - [9.7.2. Operator examples](#972-operator-examples) - - [9.7.3. Operator precedence](#973-operator-precedence) - - [9.8. Pagination](#98-pagination) - - [9.8.1. Continuation tokens](#981-continuation-tokens) - - [9.8.2. Server-driven paging](#982-server-driven-paging) - - [9.8.3. Client-driven paging](#983-client-driven-paging) - - [9.8.4. Additional considerations](#984-additional-considerations) - - [9.9. Compound collection operations](#99-compound-collection-operations) - - [9.10. Empty Results](#910-empty-results) - - [10. Delta queries](#10-delta-queries) - - [10.1. Delta links](#101-delta-links) - - [10.2. Entity representation](#102-entity-representation) - - [10.3. Obtaining a delta link](#103-obtaining-a-delta-link) - - [10.4. Contents of a delta link response](#104-contents-of-a-delta-link-response) - - [10.5. Using a delta link](#105-using-a-delta-link) - - [11. JSON standardizations](#11-json-standardizations) - - [11.1. JSON formatting standardization for primitive types](#111-json-formatting-standardization-for-primitive-types) - - [11.2. Guidelines for dates and times](#112-guidelines-for-dates-and-times) - - [11.2.1. Producing dates](#1121-producing-dates) - - [11.2.2. Consuming dates](#1122-consuming-dates) - - [11.2.3. Compatibility](#1123-compatibility) - - [11.3. JSON serialization of dates and times](#113-json-serialization-of-dates-and-times) - - [11.3.1. The `DateLiteral` format](#1131-the-dateliteral-format) - - [11.3.2. Commentary on date formatting](#1132-commentary-on-date-formatting) - - [11.4. Durations](#114-durations) - - [11.5. Intervals](#115-intervals) - - [11.6. Repeating intervals](#116-repeating-intervals) - - [12. Versioning](#12-versioning) - - [12.1. Versioning formats](#121-versioning-formats) - - [12.1.1. Group versioning](#1211-group-versioning) - - [Examples of group versioning](#examples-of-group-versioning) - - [12.2. When to version](#122-when-to-version) - - [12.3. Definition of a breaking change](#123-definition-of-a-breaking-change) - - [13. Long running operations](#13-long-running-operations) - - [13.1. Resource based long running operations (RELO)](#131-resource-based-long-running-operations-relo) - - [13.2. Stepwise long running operations](#132-stepwise-long-running-operations) - - [13.2.1. PUT](#1321-put) - - [13.2.2. POST](#1322-post) - - [13.2.3. POST, hybrid model](#1323-post-hybrid-model) - - [13.2.4. Operations resource](#1324-operations-resource) - - [13.2.5. Operation resource](#1325-operation-resource) - - [Percent complete](#percent-complete) - - [Target resource location](#target-resource-location) - - [13.2.6. Operation tombstones](#1326-operation-tombstones) - - [13.2.7. The typical flow, polling](#1327-the-typical-flow-polling) - - [Example of the typical flow, polling](#example-of-the-typical-flow-polling) - - [13.2.8. The typical flow, push notifications](#1328-the-typical-flow-push-notifications) - - [Example of the typical flow, push notifications existing subscription](#example-of-the-typical-flow-push-notifications-existing-subscription) - - [13.2.9. Retry-After](#1329-retry-after) - - [13.3. Retention policy for operation results](#133-retention-policy-for-operation-results) - - [14. Throttling, Quotas, and Limits](#14-throttling-quotas-and-limits) - - [14.1. Principles](#141-principles) - - [14.2. Return Codes (429 vs 503)](#142-return-codes-429-vs-503) - - [14.3. Retry-After and RateLimit Headers](#143-retry-after-and-ratelimit-headers) - - [14.4. Service Guidance](#144-service-guidance) - - [14.4.1. Responsiveness](#1441-responsiveness) - - [14.4.2. Rate Limits and Quotas](#1442-rate-limits-and-quotas) - - [14.4.3. Overloaded services](#1443-overloaded-services) - - [14.4.4. Example Response](#1444-example-response) - - [14.5. Caller Guidance](#145-caller-guidance) - - [14.6. Handling callers that ignore Retry-After headers](#146-handling-callers-that-ignore-retry-after-headers) - - [15. Push notifications via webhooks](#15-push-notifications-via-webhooks) - - [15.1. Scope](#151-scope) - - [15.2. Principles](#152-principles) - - [15.3. Types of subscriptions](#153-types-of-subscriptions) - - [15.4. Call sequences](#154-call-sequences) - - [15.5. Verifying subscriptions](#155-verifying-subscriptions) - - [15.6. Receiving notifications](#156-receiving-notifications) - - [15.6.1. Notification payload](#1561-notification-payload) - - [15.7. Managing subscriptions programmatically](#157-managing-subscriptions-programmatically) - - [15.7.1. Creating subscriptions](#1571-creating-subscriptions) - - [15.7.2. Updating subscriptions](#1572-updating-subscriptions) - - [15.7.3. Deleting subscriptions](#1573-deleting-subscriptions) - - [15.7.4. Enumerating subscriptions](#1574-enumerating-subscriptions) - - [15.8. Security](#158-security) - - [16. Unsupported requests](#16-unsupported-requests) - - [16.1. Essential guidance](#161-essential-guidance) - - [16.2. Feature allow list](#162-feature-allow-list) - - [16.2.1. Error response](#1621-error-response) - - [17. Naming guidelines](#17-naming-guidelines) - - [17.1. Approach](#171-approach) - - [17.2. Casing](#172-casing) - - [17.3. Names to avoid](#173-names-to-avoid) - - [17.4. Forming compound names](#174-forming-compound-names) - - [17.5. Identity properties](#175-identity-properties) - - [17.6. Date and time properties](#176-date-and-time-properties) - - [17.7. Name properties](#177-name-properties) - - [17.8. Collections and counts](#178-collections-and-counts) - - [17.9. Common property names](#179-common-property-names) - - [18. Appendix](#18-appendix) - - [18.1. Sequence diagram notes](#181-sequence-diagram-notes) - - [18.1.1. Push notifications, per user flow](#1811-push-notifications-per-user-flow) - - [18.1.2. Push notifications, firehose flow](#1812-push-notifications-firehose-flow) - - - -## 3. Introduction -Developers access most Microsoft Cloud Platform resources via HTTP interfaces. -Although each service typically provides language-specific frameworks to wrap their APIs, all of their operations eventually boil down to HTTP requests. -Microsoft must support a wide range of clients and services and cannot rely on rich frameworks being available for every development environment. -Thus, a goal of these guidelines is to ensure Microsoft REST APIs can be easily and consistently consumed by any client with basic HTTP support. - -To provide the smoothest possible experience for developers, it's important to have these APIs follow consistent design guidelines, thus making using them easy and intuitive. -This document establishes the guidelines to be followed by Microsoft REST API developers for developing such APIs consistently. - -The benefits of consistency accrue in aggregate as well; consistency allows teams to leverage common code, patterns, documentation and design decisions. - -These guidelines aim to achieve the following: -- Define consistent practices and patterns for all API endpoints across Microsoft. -- Adhere as closely as possible to accepted REST/HTTP best practices in the industry at-large. [\*] -- Make accessing Microsoft Services via REST interfaces easy for all application developers. -- Allow service developers to leverage the prior work of other services to implement, test and document REST endpoints defined consistently. -- Allow for partners (e.g., non-Microsoft entities) to use these guidelines for their own REST endpoint design. - -[\*] Note: The guidelines are designed to align with building services which comply with the REST architectural style, though they do not address or require building services that follow the REST constraints. -The term "REST" is used throughout this document to mean services that are in the spirit of REST rather than adhering to REST by the book.* - -### 3.1. Recommended reading -Understanding the philosophy behind the REST Architectural Style is recommended for developing good HTTP-based services. -If you are new to RESTful design, here are some good resources: - -[REST on Wikipedia][rest-on-wikipedia] -- Overview of common definitions and core ideas behind REST. - -[REST Dissertation][fielding] -- The chapter on REST in Roy Fielding's dissertation on Network Architecture, "Architectural Styles and the Design of Network-based Software Architectures" - -[RFC 7231][rfc-7231] -- Defines the specification for HTTP/1.1 semantics, and is considered the authoritative resource. - -[REST in Practice][rest-in-practice] -- Book on the fundamentals of REST. - -## 4. Interpreting the guidelines -### 4.1. Application of the guidelines -These guidelines are applicable to any REST API exposed publicly by Microsoft or any partner service. -Private or internal APIs SHOULD also try to follow these guidelines because internal services tend to eventually be exposed publicly. - Consistency is valuable to not only external customers but also internal service consumers, and these guidelines offer best practices useful for any service. - -There are legitimate reasons for exemption from these guidelines. -Obviously, a REST service that implements or must interoperate with some externally defined REST API must be compatible with that API and not necessarily these guidelines. -Some services MAY also have special performance needs that require a different format, such as a binary protocol. - -### 4.2. Guidelines for existing services and versioning of services -We do not recommend making a breaking change to a service that predates these guidelines simply for the sake of compliance. -The service SHOULD try to become compliant at the next version release when compatibility is being broken anyway. -When a service adds a new API, that API SHOULD be consistent with the other APIs of the same version. -So if a service was written against version 1.0 of the guidelines, new APIs added incrementally to the service SHOULD also follow version 1.0. The service can then upgrade to align with the latest version of the guidelines at the service's next major release. - -### 4.3. Requirements language -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119][rfc-2119]. - -### 4.4. License - -This work is licensed under the Creative Commons Attribution 4.0 International License. -To view a copy of this license, visit https://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. - -## 5. Taxonomy -As part of onboarding to Microsoft REST API Guidelines, services MUST comply with the taxonomy defined below. - -### 5.1. Errors -Errors, or more specifically Service Errors, are defined as a client passing invalid data to the service and the service _correctly_ rejecting that data. -Examples include invalid credentials, incorrect parameters, unknown version IDs, or similar. -These are generally "4xx" HTTP error codes and are the result of a client passing incorrect or invalid data. - -Errors do _not_ contribute to overall API availability. - -### 5.2. Faults -Faults, or more specifically Service Faults, are defined as the service failing to correctly return in response to a valid client request. -These are generally "5xx" HTTP error codes. - -Faults _do_ contribute to the overall API availability. - -Calls that fail due to rate limiting or quota failures MUST NOT count as faults. -Calls that fail as the result of a service fast-failing requests (often for its own protection) do count as faults. - -### 5.3. Latency -Latency is defined as how long a particular API call takes to complete, measured as closely to the client as possible. -This metric applies to both synchronous and asynchronous APIs in the same way. -For long running calls, the latency is measured on the initial request and measures how long that call (not the overall operation) takes to complete. - -### 5.4. Time to complete -Services that expose long operations MUST track "Time to Complete" metrics around those operations. - -### 5.5. Long running API faults -For a Long Running API, it's possible for both the initial request which begins the operation and the request which retrieves the results to technically work (each passing back a 200) but for the underlying operation to have failed. -Long Running faults MUST roll up as faults into the overall Availability metrics. - -## 6. Client guidance -To ensure the best possible experience for clients talking to a REST service, clients SHOULD adhere to the following best practices: - -### 6.1. Ignore rule -For loosely coupled clients where the exact shape of the data is not known before the call, if the server returns something the client wasn't expecting, the client MUST safely ignore it. - -Some services MAY add fields to responses without changing versions numbers. -Services that do so MUST make this clear in their documentation and clients MUST ignore unknown fields. - -### 6.2. Variable order rule -Clients MUST NOT rely on the order in which data appears in JSON service responses. -For example, clients SHOULD be resilient to the reordering of fields within a JSON object. -When supported by the service, clients MAY request that data be returned in a specific order. -For example, services MAY support the use of the _$orderBy_ querystring parameter to specify the order of elements within a JSON array. -Services MAY also explicitly specify the ordering of some elements as part of the service contract. -For example, a service MAY always return a JSON object's "type" information as the first field in an object to simplify response parsing on the client. -Clients MAY rely on ordering behavior explicitly identified by the service. - -### 6.3. Silent fail rule -Clients requesting OPTIONAL server functionality (such as optional headers) MUST be resilient to the server ignoring that particular functionality. - -## 7. Consistency fundamentals -### 7.1. URL structure -Humans SHOULD be able to easily read and construct URLs. - -This facilitates discovery and eases adoption on platforms without a well-supported client library. - -An example of a well-structured URL is: - -``` -https://api.contoso.com/v1.0/people/jdoe@contoso.com/inbox -``` - -An example URL that is not friendly is: - -``` -https://api.contoso.com/EWS/OData/Users('jdoe@microsoft.com')/Folders('AAMkADdiYzI1MjUzLTk4MjQtNDQ1Yy05YjJkLWNlMzMzYmIzNTY0MwAuAAAAAACzMsPHYH6HQoSwfdpDx-2bAQCXhUk6PC1dS7AERFluCgBfAAABo58UAAA=') -``` - -A frequent pattern that comes up is the use of URLs as values. -Services MAY use URLs as values. -For example, the following is acceptable: - -``` -https://api.contoso.com/v1.0/items?url=https://resources.contoso.com/shoes/fancy -``` - -### 7.2. URL length -The HTTP 1.1 message format, defined in RFC 7230, in section [3.1.1][rfc-7230-3-1-1], defines no length limit on the Request Line, which includes the target URL. -From the RFC: - -> HTTP does not place a predefined limit on the length of a - request-line. [...] A server that receives a request-target longer than any URI it wishes to parse MUST respond - with a 414 (URI Too Long) status code. - -Services that can generate URLs longer than 2,083 characters MUST make accommodations for the clients they wish to support. -Here are some sources for determining what target clients support: - - * [https://stackoverflow.com/a/417184](https://stackoverflow.com/a/417184) - * [https://blogs.msdn.microsoft.com/ieinternals/2014/08/13/url-length-limits/](https://blogs.msdn.microsoft.com/ieinternals/2014/08/13/url-length-limits/) - -Also note that some technology stacks have hard and adjustable URL limits, so keep this in mind as you design your services. - -### 7.3. Canonical identifier -In addition to friendly URLs, resources that can be moved or be renamed SHOULD expose a URL that contains a unique stable identifier. -It MAY be necessary to interact with the service to obtain a stable URL from the friendly name for the resource, as in the case of the "/my" shortcut used by some services. - -The stable identifier is not required to be a GUID. - -An example of a URL containing a canonical identifier is: - -``` -https://api.contoso.com/v1.0/people/7011042402/inbox -``` - -### 7.4. Supported methods -Operations MUST use the proper HTTP methods whenever possible, and operation idempotency MUST be respected. -HTTP methods are frequently referred to as the HTTP verbs. -The terms are synonymous in this context, however the HTTP specification uses the term method. - -Below is a list of methods that Microsoft REST services SHOULD support. -Not all resources will support all methods, but all resources using the methods below MUST conform to their usage. - -Method | Description | Is Idempotent -------- | -------------------------------------------------------------------------------------------------------------------------- | ------------- -GET | Return the current value of an object | True -PUT | Replace an object, or create a named object, when applicable | True -DELETE | Delete an object | True -POST | Create a new object based on the data provided, or submit a command | False -HEAD | Return metadata of an object for a GET response. Resources that support the GET method MAY support the HEAD method as well | True -PATCH | Apply a partial update to an object | False -OPTIONS | Get information about a request; see below for details. | True - -Table 1 - -#### 7.4.1. POST -POST operations SHOULD support the Location response header to specify the location of any created resource that was not explicitly named, via the Location header. - -As an example, imagine a service that allows creation of hosted servers, which will be named by the service: - -```http -POST http://api.contoso.com/account1/servers -``` - -The response would be something like: - -```http -201 Created -Location: http://api.contoso.com/account1/servers/server321 -``` - -Where "server321" is the service-allocated server name. - -Services MAY also return the full metadata for the created item in the response. - -#### 7.4.2. PATCH -PATCH has been standardized by IETF as the method to be used for updating an existing object incrementally (see [RFC 5789][rfc-5789]). -Microsoft REST API Guidelines compliant APIs SHOULD support PATCH. - -#### 7.4.3. Creating resources via PATCH (UPSERT semantics) -Services that allow callers to specify key values on create SHOULD support UPSERT semantics, and those that do MUST support creating resources using PATCH. -Because PUT is defined as a complete replacement of the content, it is dangerous for clients to use PUT to modify data. -Clients that do not understand (and hence ignore) properties on a resource are not likely to provide them on a PUT when trying to update a resource, hence such properties could be inadvertently removed. -Services MAY optionally support PUT to update existing resources, but if they do they MUST use replacement semantics (that is, after the PUT, the resource's properties MUST match what was provided in the request, including deleting any server properties that were not provided). - -Under UPSERT semantics, a PATCH call to a nonexistent resource is handled by the server as a "create", and a PATCH call to an existing resource is handled as an "update". To ensure that an update request is not treated as a create or vice versa, the client MAY specify precondition HTTP headers in the request. -The service MUST NOT treat a PATCH request as an insert if it contains an If-Match header and MUST NOT treat a PATCH request as an update if it contains an If-None-Match header with a value of "*". - -If a service does not support UPSERT, then a PATCH call against a resource that does not exist MUST result in an HTTP "409 Conflict" error. - -#### 7.4.4. Options and link headers -OPTIONS allows a client to retrieve information about a resource, at a minimum by returning the Allow header denoting the valid methods for this resource. - -In addition, services SHOULD include a Link header (see [RFC 5988][rfc-5988]) to point to documentation for the resource in question: - -```http -Link: <{help}>; rel="help" -``` - -Where {help} is the URL to a documentation resource. - -For examples on use of OPTIONS, see [preflighting CORS cross-domain calls][cors-preflight]. - -### 7.5. Standard request headers -The table of request headers below SHOULD be used by Microsoft REST API Guidelines services. -Using these headers is not mandated, but if used they MUST be used consistently. - -All header values MUST follow the syntax rules set forth in the specification where the header field is defined. -Many HTTP headers are defined in [RFC7231][rfc-7231], however a complete list of approved headers can be found in the [IANA Header Registry][IANA-headers]." - -Header | Type | Description ---------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Authorization | String | Authorization header for the request -Date | Date | Timestamp of the request, based on the client's clock, in [RFC 5322][rfc-5322-3-3] date and time format. The server SHOULD NOT make any assumptions about the accuracy of the client's clock. This header MAY be included in the request, but MUST be in this format when supplied. Greenwich Mean Time (GMT) MUST be used as the time zone reference for this header when it is provided. For example: `Wed, 24 Aug 2016 18:41:30 GMT`. Note that GMT is exactly equal to UTC (Coordinated Universal Time) for this purpose. -Accept | Content type | The requested content type for the response such as:
    • application/xml
    • text/xml
    • application/json
    • text/javascript (for JSONP)
    Per the HTTP guidelines, this is just a hint and responses MAY have a different content type, such as a blob fetch where a successful response will just be the blob stream as the payload. For services following OData, the preference order specified in OData SHOULD be followed. -Accept-Encoding | Gzip, deflate | REST endpoints SHOULD support GZIP and DEFLATE encoding, when applicable. For very large resources, services MAY ignore and return uncompressed data. -Accept-Language | "en", "es", etc. | Specifies the preferred language for the response. Services are not required to support this, but if a service supports localization it MUST do so through the Accept-Language header. -Accept-Charset | Charset type like "UTF-8" | Default is UTF-8, but services SHOULD be able to handle ISO-8859-1. -Content-Type | Content type | Mime type of request body (PUT/POST/PATCH) -Prefer | return=minimal, return=representation | If the return=minimal preference is specified, services SHOULD return an empty body in response to a successful insert or update. If return=representation is specified, services SHOULD return the created or updated resource in the response. Services SHOULD support this header if they have scenarios where clients would sometimes benefit from responses, but sometimes the response would impose too much of a hit on bandwidth. -If-Match, If-None-Match, If-Range | String | Services that support updates to resources using optimistic concurrency control MUST support the If-Match header to do so. Services MAY also use other headers related to ETags as long as they follow the HTTP specification. - -### 7.6. Standard response headers -Services SHOULD return the following response headers, except where noted in the "required" column. - -Response Header | Required | Description ------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Date | All responses | Timestamp the response was processed, based on the server's clock, in [RFC 5322][rfc-5322-3-3] date and time format. This header MUST be included in the response. Greenwich Mean Time (GMT) MUST be used as the time zone reference for this header. For example: `Wed, 24 Aug 2016 18:41:30 GMT`. Note that GMT is exactly equal to UTC (Coordinated Universal Time) for this purpose. -Content-Type | All responses | The content type -Content-Encoding | All responses | GZIP or DEFLATE, as appropriate -Preference-Applied | When specified in request | Whether a preference indicated in the Prefer request header was applied -ETag | When the requested resource has an entity tag | The ETag response-header field provides the current value of the entity tag for the requested variant. Used with If-Match, If-None-Match and If-Range to implement optimistic concurrency control. - -### 7.7. Custom headers -Custom headers MUST NOT be required for the basic operation of a given API. - -Some of the guidelines in this document prescribe the use of nonstandard HTTP headers. -In addition, some services MAY need to add extra functionality, which is exposed via HTTP headers. -The following guidelines help maintain consistency across usage of custom headers. - -Headers that are not standard HTTP headers MUST have one of two formats: - -1. A generic format for headers that are registered as "provisional" with IANA ([RFC 3864][rfc-3864]) -2. A scoped format for headers that are too usage-specific for registration - -These two formats are described below. - -### 7.8. Specifying headers as query parameters -Some headers pose challenges for some scenarios such as AJAX clients, especially when making cross-domain calls where adding headers MAY not be supported. -As such, some headers MAY be accepted as Query Parameters in addition to headers, with the same naming as the header: - -Not all headers make sense as query parameters, including most standard HTTP headers. - -The criteria for considering when to accept headers as parameters are: - -1. Any custom headers MUST be also accepted as parameters. -2. Required standard headers MAY be accepted as parameters. -3. Required headers with security sensitivity (e.g., Authorization header) MIGHT NOT be appropriate as parameters; the service owner SHOULD evaluate these on a case-by-case basis. - -The one exception to this rule is the Accept header. -It's common practice to use a scheme with simple names instead of the full functionality described in the HTTP specification for Accept. - -### 7.9. PII parameters -Consistent with their organization's privacy policy, clients SHOULD NOT transmit personally identifiable information (PII) parameters in the URL (as part of path or query string) because this information can be inadvertently exposed via client, network, and server logs and other mechanisms. - -Consequently, a service SHOULD accept PII parameters transmitted as headers. - -However, there are many scenarios where the above recommendations cannot be followed due to client or software limitations. -To address these limitations, services SHOULD also accept these PII parameters as part of the URL consistent with the rest of these guidelines. - -Services that accept PII parameters -- whether in the URL or as headers -- SHOULD be compliant with privacy policy specified by their organization's engineering leadership. -This will typically include recommending that clients prefer headers for transmission and implementations adhere to special precautions to ensure that logs and other service data collection are properly handled. - -### 7.10. Response formats -For organizations to have a successful platform, they must serve data in formats developers are accustomed to using, and in consistent ways that allow developers to handle responses with common code. - -Web-based communication, especially when a mobile or other low-bandwidth client is involved, has moved quickly in the direction of JSON for a variety of reasons, including its tendency to be lighter weight and its ease of consumption with JavaScript-based clients. - -JSON property names SHOULD be camelCased. - -Services SHOULD provide JSON as the default encoding. - -#### 7.10.1. Clients-specified response format -In HTTP, response format SHOULD be requested by the client using the Accept header. -This is a hint, and the server MAY ignore it if it chooses to, even if this isn't typical of well-behaved servers. -Clients MAY send multiple Accept headers and the service MAY choose one of them. - -The default response format (no Accept header provided) SHOULD be application/json, and all services MUST support application/json. - -Accept Header | Response type | Notes ----------------- | ---------------------------------- | ------------------------------------------- -application/json | Payload SHOULD be returned as JSON | Also accept text/javascript for JSONP cases - -```http -GET https://api.contoso.com/v1.0/products/user -Accept: application/json -``` - -#### 7.10.2. Error condition responses -For non-success conditions, developers SHOULD be able to write one piece of code that handles errors consistently across different Microsoft REST API Guidelines services. -This allows building of simple and reliable infrastructure to handle exceptions as a separate flow from successful responses. -The following is based on the OData v4 JSON spec. -However, it is very generic and does not require specific OData constructs. -APIs SHOULD use this format even if they are not using other OData constructs. - -The error response MUST be a single JSON object. -This object MUST have a name/value pair named "error". The value MUST be a JSON object. - -This object MUST contain name/value pairs with the names "code" and "message", and it MAY contain name/value pairs with the names "target", "details" and "innererror." - -The value for the "code" name/value pair is a language-independent string. -Its value is a service-defined error code that SHOULD be human-readable. -This code serves as a more specific indicator of the error than the HTTP error code specified in the response. -Services SHOULD have a relatively small number (about 20) of possible values for "code", and all clients MUST be capable of handling all of them. -Most services will require a much larger number of more specific error codes, which are not interesting to all clients. -These error codes SHOULD be exposed in the "innererror" name/value pair as described below. -Introducing a new value for "code" that is visible to existing clients is a breaking change and requires a version increase. -Services can avoid breaking changes by adding new error codes to "innererror" instead. - -The value for the "message" name/value pair MUST be a human-readable representation of the error. -It is intended as an aid to developers and is not suitable for exposure to end users. -Services wanting to expose a suitable message for end users MUST do so through an [annotation][odata-json-annotations] or custom property. -Services SHOULD NOT localize "message" for the end user, because doing so might make the value unreadable to the app developer who may be logging the value, as well as make the value less searchable on the Internet. - -The value for the "target" name/value pair is the target of the particular error (e.g., the name of the property in error). - -The value for the "details" name/value pair MUST be an array of JSON objects that MUST contain name/value pairs for "code" and "message", and MAY contain a name/value pair for "target", as described above. -The objects in the "details" array usually represent distinct, related errors that occurred during the request. -See example below. - -The value for the "innererror" name/value pair MUST be an object. -The contents of this object are service-defined. -Services wanting to return more specific errors than the root-level code MUST do so by including a name/value pair for "code" and a nested "innererror". Each nested "innererror" object represents a higher level of detail than its parent. -When evaluating errors, clients MUST traverse through all of the nested "innererrors" and choose the deepest one that they understand. -This scheme allows services to introduce new error codes anywhere in the hierarchy without breaking backwards compatibility, so long as old error codes still appear. -The service MAY return different levels of depth and detail to different callers. -For example, in development environments, the deepest "innererror" MAY contain internal information that can help debug the service. -To guard against potential security concerns around information disclosure, services SHOULD take care not to expose too much detail unintentionally. -Error objects MAY also include custom server-defined name/value pairs that MAY be specific to the code. -Error types with custom server-defined properties SHOULD be declared in the service's metadata document. -See example below. - -Error responses MAY contain [annotations][odata-json-annotations] in any of their JSON objects. - -We recommend that for any transient errors that may be retried, services SHOULD include a Retry-After HTTP header indicating the minimum number of seconds that clients SHOULD wait before attempting the operation again. - -##### ErrorResponse : Object - -Property | Type | Required | Description --------- | ---- | -------- | ----------- -`error` | Error | ✔ | The error object. - -##### Error : Object - -Property | Type | Required | Description --------- | ---- | -------- | ----------- -`code` | String | ✔ | One of a server-defined set of error codes. -`message` | String | ✔ | A human-readable representation of the error. -`target` | String | | The target of the error. -`details` | Error[] | | An array of details about specific errors that led to this reported error. -`innererror` | InnerError | | An object containing more specific information than the current object about the error. - -##### InnerError : Object - -Property | Type | Required | Description --------- | ---- | -------- | ----------- -`code` | String | | A more specific error code than was provided by the containing error. -`innererror` | InnerError | | An object containing more specific information than the current object about the error. - -##### Examples - -Example of "innererror": - -```json -{ - "error": { - "code": "BadArgument", - "message": "Previous passwords may not be reused", - "target": "password", - "innererror": { - "code": "PasswordError", - "innererror": { - "code": "PasswordDoesNotMeetPolicy", - "minLength": "6", - "maxLength": "64", - "characterTypes": ["lowerCase","upperCase","number","symbol"], - "minDistinctCharacterTypes": "2", - "innererror": { - "code": "PasswordReuseNotAllowed" - } - } - } - } -} -``` - -In this example, the most basic error code is "BadArgument", but for clients that are interested, there are more specific error codes in "innererror." -The "PasswordReuseNotAllowed" code may have been added by the service at a later date, having previously only returned "PasswordDoesNotMeetPolicy." -Existing clients do not break when the new error code is added, but new clients MAY take advantage of it. -The "PasswordDoesNotMeetPolicy" error also includes additional name/value pairs that allow the client to determine the server's configuration, validate the user's input programmatically, or present the server's constraints to the user within the client's own localized messaging. - -Example of "details": - -```json -{ - "error": { - "code": "BadArgument", - "message": "Multiple errors in ContactInfo data", - "target": "ContactInfo", - "details": [ - { - "code": "NullValue", - "target": "PhoneNumber", - "message": "Phone number must not be null" - }, - { - "code": "NullValue", - "target": "LastName", - "message": "Last name must not be null" - }, - { - "code": "MalformedValue", - "target": "Address", - "message": "Address is not valid" - } - ] - } -} -``` - -In this example there were multiple problems with the request, with each individual error listed in "details." - -### 7.11. HTTP Status Codes -Standard HTTP Status Codes SHOULD be used; see the HTTP Status Code definitions for more information. - -### 7.12. Client library optional -Developers MUST be able to develop on a wide variety of platforms and languages, such as Windows, macOS, Linux, C#, Python, Node.js, and Ruby. - -Services SHOULD be able to be accessed from simple HTTP tools such as curl without significant effort. - -Service developer portals SHOULD provide the equivalent of "Get Developer Token" to facilitate experimentation and curl support. - -## 8. CORS -Services compliant with the Microsoft REST API Guidelines MUST support [CORS (Cross Origin Resource Sharing)][cors]. -Services SHOULD support an allowed origin of CORS * and enforce authorization through valid OAuth tokens. -Services SHOULD NOT support user credentials with origin validation. -There MAY be exceptions for special cases. - -### 8.1. Client guidance -Web developers usually don't need to do anything special to take advantage of CORS. -All of the handshake steps happen invisibly as part of the standard XMLHttpRequest calls they make. - -Many other platforms, such as .NET, have integrated support for CORS. - -#### 8.1.1. Avoiding preflight -Because the CORS protocol can trigger preflight requests that add additional round trips to the server, performance-critical apps might be interested in avoiding them. -The spirit behind CORS is to avoid preflight for any simple cross-domain requests that old non-CORS-capable browsers were able to make. -All other requests require preflight. - -A request is "simple" and avoids preflight if its method is GET, HEAD or POST, and if it doesn't contain any request headers besides Accept, Accept-Language and Content-Language. -For POST requests, the Content-Type header is also allowed, but only if its value is "application/x-www-form-urlencoded", "multipart/form-data" or "text/plain." -For any other headers or values, a preflight request will happen. - -### 8.2. Service guidance - At minimum, services MUST: -- Understand the Origin request header that browsers send on cross-domain requests, and the Access-Control-Request-Method request header that they send on preflight OPTIONS requests that check for access. -- If the Origin header is present in a request: - - If the request uses the OPTIONS method and contains the Access-Control-Request-Method header, then it is a preflight request intended to probe for access before the actual request. Otherwise, it is an actual request. For preflight requests, beyond performing the steps below to add headers, services MUST perform no additional processing and MUST return a 200 OK. For non-preflight requests, the headers below are added in addition to the request's regular processing. - - Add an Access-Control-Allow-Origin header to the response, containing the same value as the Origin request header. Note that this requires services to dynamically generate the header value. Resources that do not require cookies or any other form of [user credentials][cors-user-credentials] MAY respond with a wildcard asterisk (*) instead. Note that the wildcard is acceptable here only, and not for any of the other headers described below. - - If the caller requires access to a response header that is not in the set of [simple response headers][cors-simple-headers] (Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma), then add an Access-Control-Expose-Headers header containing the list of additional response header names the client should have access to. - - If the request requires cookies, then add an Access-Control-Allow-Credentials header set to "true." - - If the request was a preflight request (see first bullet), then the service MUST: - - Add an Access-Control-Allow-Headers response header containing the list of request header names the client is permitted to use. This list need only contain headers that are not in the set of [simple request headers][cors-simple-headers] (Accept, Accept-Language, Content-Language). If there are no restrictions on headers the service accepts, the service MAY simply return the same value as the Access-Control-Request-Headers header sent by the client. - - Add an Access-Control-Allow-Methods response header containing the list of HTTP methods the caller is permitted to use. - -Add an Access-Control-Max-Age pref response header containing the number of seconds for which this preflight response is valid (and hence can be avoided before subsequent actual requests). Note that while it is customary to use a large value like 2592000 (30 days), many browsers self-impose a much lower limit (e.g., five minutes). - -Because browser preflight response caches are notoriously weak, the additional round trip from a preflight response hurts performance. -Services used by interactive Web clients where performance is critical SHOULD avoid patterns that cause a preflight request -- For GET and HEAD calls, avoid requiring request headers that are not part of the simple set above. Allow them to be provided as query parameters instead. - - The Authorization header is not part of the simple set, so the authentication token MUST be sent through the "access_token" query parameter instead, for resources requiring authentication. Note that passing authentication tokens in the URL is not recommended, because it can lead to the token getting recorded in server logs and exposed to anyone with access to those logs. Services that accept authentication tokens through the URL MUST take steps to mitigate the security risks, such as using short-lived authentication tokens, suppressing the auth token from getting logged, and controlling access to server logs. - -- Avoid requiring cookies. XmlHttpRequest will only send cookies on cross-domain requests if the "withCredentials" attribute is set; this also causes a preflight request. - - Services that require cookie-based authentication MUST use a "dynamic canary" to secure all APIs that accept cookies. - -- For POST calls, prefer simple Content-Types in the set of ("application/x-www-form-urlencoded", "multipart/form-data", "text/plain") where applicable. Any other Content-Type will induce a preflight request. - - Services MUST NOT contravene other API recommendations in the name of avoiding CORS preflight requests. In particular, in accordance with recommendations, most POST requests will actually require a preflight request due to the Content-Type. - - If eliminating preflight is critical, then a service MAY support alternative mechanisms for data transfer, but the RECOMMENDED approach MUST also be supported. - -In addition, when appropriate services MAY support the JSONP pattern for simple, GET-only cross-domain access. -In JSONP, services take a parameter indicating the format (_$format=json_) and a parameter indicating a callback (_$callback=someFunc_), and return a text/javascript document containing the JSON response wrapped in a function call with the indicated name. -More on JSONP at Wikipedia: [JSONP](https://en.wikipedia.org/wiki/JSONP). - -## 9. Collections -### 9.1. Item keys -Services MAY support durable identifiers for each item in the collection, and that identifier SHOULD be represented in JSON as "id". These durable identifiers are often used as item keys. - -Collections that support durable identifiers MAY support delta queries. - -### 9.2. Serialization -Collections are represented in JSON using standard array notation. - -### 9.3. Collection URL patterns -Collections are located directly under the service root when they are top level, or as a segment under another resource when scoped to that resource. - -For example: - -```http -GET https://api.contoso.com/v1.0/people -``` - -Whenever possible, services MUST support the "/" pattern. -For example: - -```http -GET https://{serviceRoot}/{collection}/{id} -``` - -Where: -- {serviceRoot} – the combination of host (site URL) + the root path to the service -- {collection} – the name of the collection, unabbreviated, pluralized -- {id} – the value of the unique id property. When using the "/" pattern this MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. - -#### 9.3.1. Nested collections and properties -Collection items MAY contain other collections. -For example, a user collection MAY contain user resources that have multiple addresses: - -```http -GET https://api.contoso.com/v1.0/people/123/addresses -``` - -```json -{ - "value": [ - { "street": "1st Avenue", "city": "Seattle" }, - { "street": "124th Ave NE", "city": "Redmond" } - ] -} -``` - -### 9.4. Big collections -As data grows, so do collections. -Planning for pagination is important for all services. -Therefore, when multiple pages are available, the serialization payload MUST contain the opaque URL for the next page as appropriate. -Refer to the paging guidance for more details. - -Clients MUST be resilient to collection data being either paged or nonpaged for any given request. - -```json -{ - "value":[ - { "id": "Item 1","price": 99.95,"sizes": null}, - { … }, - { … }, - { "id": "Item 99","price": 59.99,"sizes": null} - ], - "@nextLink": "{opaqueUrl}" -} -``` - -### 9.5. Changing collections -POST requests are not idempotent. -This means that two POST requests sent to a collection resource with exactly the same payload MAY lead to multiple items being created in that collection. -This is often the case for insert operations on items with a server-side generated id. - -For example, the following request: - -```http -POST https://api.contoso.com/v1.0/people -``` - -Would lead to a response indicating the location of the new collection item: - -```http -201 Created -Location: https://api.contoso.com/v1.0/people/123 -``` - -And once executed again, would likely lead to another resource: - -```http -201 Created -Location: https://api.contoso.com/v1.0/people/124 -``` - -While a PUT request would require the indication of the collection item with the corresponding key instead: - -```http -PUT https://api.contoso.com/v1.0/people/123 -``` - -### 9.6. Sorting collections -The results of a collection query MAY be sorted based on property values. -The property is determined by the value of the _$orderBy_ query parameter. - -The value of the _$orderBy_ parameter contains a comma-separated list of expressions used to sort the items. -A special case of such an expression is a property path terminating on a primitive property. - -The expression MAY include the suffix "asc" for ascending or "desc" for descending, separated from the property name by one or more spaces. -If "asc" or "desc" is not specified, the service MUST order by the specified property in ascending order. - -NULL values MUST sort as "less than" non-NULL values. - -Items MUST be sorted by the result values of the first expression, and then items with the same value for the first expression are sorted by the result value of the second expression, and so on. -The sort order is the inherent order for the type of the property. - -For example: - -```http -GET https://api.contoso.com/v1.0/people?$orderBy=name -``` - -Will return all people sorted by name in ascending order. - -For example: - -```http -GET https://api.contoso.com/v1.0/people?$orderBy=name desc -``` - -Will return all people sorted by name in descending order. - -Sub-sorts can be specified by a comma-separated list of property names with OPTIONAL direction qualifier. - -For example: - -```http -GET https://api.contoso.com/v1.0/people?$orderBy=name desc,hireDate -``` - -Will return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. - -Sorting MUST compose with filtering such that: - -```http -GET https://api.contoso.com/v1.0/people?$filter=name eq 'david'&$orderBy=hireDate -``` - -Will return all people whose name is David sorted in ascending order by hireDate. - -#### 9.6.1. Interpreting a sorting expression -Sorting parameters MUST be consistent across pages, as both client and server-side paging is fully compatible with sorting. - -If a service does not support sorting by a property named in a _$orderBy_ expression, the service MUST respond with an error message as defined in the Responding to Unsupported Requests section. - -### 9.7. Filtering -The _$filter_ querystring parameter allows clients to filter a collection of resources that are addressed by a request URL. -The expression specified with _$filter_ is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. -Resources for which the expression evaluates to false or to null, or which reference properties that are unavailable due to permissions, are omitted from the response. - -Example: return all Products whose Price is less than $10.00 - -```http -GET https://api.contoso.com/v1.0/products?$filter=price lt 10.00 -``` - -The value of the _$filter_ option is a Boolean expression. - -#### 9.7.1. Filter operations -Services that support _$filter_ SHOULD support the following minimal set of operations. - -Operator | Description | Example --------------------- | --------------------- | ----------------------------------------------------- -Comparison Operators | | -eq | Equal | city eq 'Redmond' -ne | Not equal | city ne 'London' -gt | Greater than | price gt 20 -ge | Greater than or equal | price ge 10 -lt | Less than | price lt 20 -le | Less than or equal | price le 100 -Logical Operators | | -and | Logical and | price le 200 and price gt 3.5 -or | Logical or | price le 3.5 or price gt 200 -not | Logical negation | not price le 3.5 -Grouping Operators | | -( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 - -#### 9.7.2. Operator examples -The following examples illustrate the use and semantics of each of the logical operators. - -Example: all products with a name equal to 'Milk' - -```http -GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' -``` - -Example: all products with a name not equal to 'Milk' - -```http -GET https://api.contoso.com/v1.0/products?$filter=name ne 'Milk' -``` - -Example: all products with the name 'Milk' that also have a price less than 2.55: - -```http -GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' and price lt 2.55 -``` - -Example: all products that either have the name 'Milk' or have a price less than 2.55: - -```http -GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' or price lt 2.55 -``` - -Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: - -```http -GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 -``` - -#### 9.7.3. Operator precedence -Services MUST use the following operator precedence for supported operators when evaluating _$filter_ expressions. -Operators are listed by category in order of precedence from highest to lowest. -Operators in the same category have equal precedence: - -| Group | Operator | Description | -|:----------------|:---------|:----------------------| -| Grouping | ( ) | Precedence grouping | -| Unary | not | Logical Negation | -| Relational | gt | Greater Than | -| | ge | Greater than or Equal | -| | lt | Less Than | -| | le | Less than or Equal | -| Equality | eq | Equal | -| | ne | Not Equal | -| Conditional AND | and | Logical And | -| Conditional OR | or | Logical Or | - -### 9.8. Pagination -RESTful APIs that return collections MAY return partial sets. -Consumers of these services MUST expect partial result sets and correctly page through to retrieve an entire set. - -There are two forms of pagination that MAY be supported by RESTful APIs. -Server-driven paging mitigates against denial-of-service attacks by forcibly paginating a request over multiple response payloads. -Client-driven paging enables clients to request only the number of resources that it can use at a given time. - -Sorting and Filtering parameters MUST be consistent across pages, because both client- and server-side paging is fully compatible with both filtering and sorting. - -#### 9.8.1. Continuation tokens - -Paginated responses MUST indicate a partial result by including a continuation token in the response using the OData control information `@nextLink`. -The absence of a continuation token means that no additional pages are available. - -Clients MUST treat the continuation URL as opaque, which means that query options may not be changed while iterating over a set of partial results. - -Example: - -```http -GET http://api.contoso.com/v1.0/people HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - ..., - "value": [...], - "@nextLink": "{opaqueUrl}" -} -``` - -The `@nextLink` MAY be populated using either server-driven paging or client-driven paging (`@nextLink`s generated using client-driven paging should not include the `$top` query parameter). - -#### 9.8.2. Server-driven paging - -The server MAY provide server-driven paging by populating the continuation token with a `$skiptoken` query parameter. -The `$skiptoken` value is opaque for clients and its structure should not be assumed. -`$skiptoken` values SHOULD expire after some period of time decided by the server. - -Example: - -```http -GET http://api.contoso.com/v1.0/people HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - ..., - "value": [...], - "@nextLink": "http://api.contoso.com/v1.0/people?$skiptoken={opaquetoken}" -} -``` - -```http -GET http://api.contoso.com/v1.0/people?$skiptoken={opaquetoken} HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - ..., - "value": [...], - "@nextLink": "http://api.contoso.com/v1.0/people?$skiptoken={opaquetoken2}" -} -``` - -#### 9.8.3. Client-driven paging -Clients MAY use _$top_ and _$skip_ query parameters to specify a number of results to return and an offset into the collection. - -The server SHOULD honor the values specified by the client; however, clients MUST be prepared to handle responses that contain a different page size or contain a continuation token. - -When both _$top_ and _$skip_ are given by a client, the server SHOULD first apply _$skip_ and then _$top_ on the collection. - -Note: If the server can't honor _$top_ and/or _$skip_, the server MUST return an error to the client informing about it instead of just ignoring the query options. -This will avoid the risk of the client making assumptions about the data returned. - -Example: - -```http -GET http://api.contoso.com/v1.0/people?$top=5&$skip=2 HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - ..., - "value": [...] -} -``` - -#### 9.8.4. Additional considerations -**Stable order prerequisite:** Both forms of paging depend on the collection of items having a stable order. -The server MUST supplement any specified order criteria with additional sorts (typically by key) to ensure that items are always ordered consistently. - -**Missing/repeated results:** Even if the server enforces a consistent sort order, results MAY be missing or repeated based on creation or deletion of other resources. -Clients MUST be prepared to deal with these discrepancies. -The server SHOULD always encode the record ID of the last read record, helping the client in the process of managing repeated/missing results. - -**Combining client- and server-driven paging:** Note that client-driven paging does not preclude server-driven paging. -If the page size requested by the client is larger than the default page size supported by the server, the expected response would be the number of results specified by the client, paginated as specified by the server paging settings. - -**Page Size:** Clients MAY request server-driven paging with a specific page size by specifying a _$maxpagesize_ preference. -The server SHOULD honor this preference if the specified page size is smaller than the server's default page size. - -**Paginating embedded collections:** It is possible for both client-driven paging and server-driven paging to be applied to embedded collections. -If a server paginates an embedded collection, it MUST include additional continuation tokens as appropriate. - -**Recordset count:** Developers who want to know the full number of records across all pages, MAY include the query parameter _$count=true_ to tell the server to include the count of items in the response. - -### 9.9. Compound collection operations -Filtering, Sorting and Pagination operations MAY all be performed against a given collection. -When these operations are performed together, the evaluation order MUST be: - -1. **Filtering**. This includes all range expressions performed as an AND operation. -2. **Sorting**. The potentially filtered list is sorted according to the sort criteria. -3. **Pagination**. The materialized paginated view is presented over the filtered, sorted list. This applies to both server-driven pagination and client-driven pagination. - -### 9.10. Empty Results -When a filter is performed on a collection and the result set is empty you MUST respond with a valid response body and a 200 response code. -In this example the filters supplied by the client resulted in a empty result set. -The response body is returned as normal and the _value_ attribute is set to a empty collection. -A client MAY be expecting metadata attributes like _maxItems_ based on the format of your responses to similar calls which produced results. -You SHOULD maintain consistency in your API whenever possible. - -```http -GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - ..., - "maxItems": 0, - "value": [] -} -``` - -## 10. Delta queries -Services MAY choose to support delta queries. - -### 10.1. Delta links -Delta links are opaque, service-generated links that the client uses to retrieve subsequent changes to a result. - -At a conceptual level delta links are based on a defining query that describes the set of results for which changes are being tracked. -The delta link encodes the collection of entities for which changes are being tracked, along with a starting point from which to track changes. - -If the query contains a filter, the response MUST include only changes to entities matching the specified criteria. -The key principles of the Delta Query are: -- Every item in the set MUST have a persistent identifier. That identifier SHOULD be represented as "id". This identifier is a service defined opaque string that MAY be used by the client to track object across calls. -- The delta MUST contain an entry for each entity that newly matches the specified criteria, and MUST contain a "@removed" entry for each entity that no longer matches the criteria. -- Re-evaluate the query and compare it to original set of results; every entry uniquely in the current set MUST be returned as an Add operation, and every entry uniquely in the original set MUST be returned as a "remove" operation. -- Each entity that previously did not match the criteria but matches it now MUST be returned as an "add"; conversely, each entity that previously matched the query but no longer does MUST be returned as a "@removed" entry. -- Entities that have changed MUST be included in the set using their standard representation. -- Services MAY add additional metadata to the "@removed" node, such as a reason for removal, or a "removed at" timestamp. We recommend teams coordinate with the Microsoft REST API Guidelines Working Group on extensions to help maintain consistency. - -The delta link MUST NOT encode any client top or skip value. - -### 10.2. Entity representation -Added and updated entities are represented in the entity set using their standard representation. -From the perspective of the set, there is no difference between an added or updated entity. - -Removed entities are represented using only their "id" and an "@removed" node. -The presence of an "@removed" node MUST represent the removal of the entry from the set. - -### 10.3. Obtaining a delta link -A delta link is obtained by querying a collection or entity and appending a $delta query string parameter. -For example: - -```http -GET https://api.contoso.com/v1.0/people?$delta -HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "value":[ - { "id": "1", "name": "Matt"}, - { "id": "2", "name": "Mark"}, - { "id": "3", "name": "John"} - ], - "@deltaLink": "{opaqueUrl}" -} -``` - -Note: If the collection is paginated the deltaLink will only be present on the final page but MUST reflect any changes to the data returned across all pages. - -### 10.4. Contents of a delta link response -Added/Updated entries MUST appear as regular JSON objects, with regular item properties. -Returning the added/modified items in their regular representation allows the client to merge them into their existing "cache" using standard merge concepts based on the "id" field. - -Entries removed from the defined collection MUST be included in the response. -Items removed from the set MUST be represented using only their "id" and an "@removed" node. - -### 10.5. Using a delta link -The client requests changes by invoking the GET method on the delta link. -The client MUST use the delta URL as is -- in other words the client MUST NOT modify the URL in any way (e.g., parsing it and adding additional query string parameters). -In this example: - -```http -GET https://{opaqueUrl} HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "value":[ - { "id": "1", "name": "Mat"}, - { "id": "2", "name": "Marc"}, - { "id": "3", "@removed": {} }, - { "id": "4", "name": "Luc"} - ], - "@deltaLink": "{opaqueUrl}" -} -``` - -The results of a request against the delta link may span multiple pages but MUST be ordered by the service across all pages in such a way as to ensure a deterministic result when applied in order to the response that contained the delta link. - -If no changes have occurred, the response is an empty collection that contains a delta link for subsequent changes if requested. -This delta link MAY be identical to the delta link resulting in the empty collection of changes. - -If the delta link is no longer valid, the service MUST respond with _410 Gone_. The response SHOULD include a Location header that the client can use to retrieve a new baseline set of results. - -## 11. JSON standardizations -### 11.1. JSON formatting standardization for primitive types -Primitive values MUST be serialized to JSON following the rules of [RFC8259][rfc-8259]. - -**Important note for 64bit integers:** JavaScript will silently truncate integers larger than `Number.MAX_SAFE_INTEGER` (2^53-1) or numbers smaller than `Number.MIN_SAFE_INTEGER` (-2^53+1). If the service is expected to return integer values outside the range of safe values, strongly consider returning the value as a string in order to maximize interoperability and avoid data loss. - -### 11.2. Guidelines for dates and times -#### 11.2.1. Producing dates -Services MUST produce dates using the `DateLiteral` format, and SHOULD use the `Iso8601Literal` format unless there are compelling reasons to do otherwise. -Services that do use the `StructuredDateLiteral` format MUST NOT produce dates using the `T` kind unless BOTH the additional precision is REQUIRED, and ECMAScript clients are explicitly unsupported. -(Non-Normative statement: When deciding which particular `DateKind` to standardize on, the approximate order of preference is `E, C, U, W, O, X, I, T`. -This optimizes for ECMAScript, .NET, and C++ programmers, in that order.) - -#### 11.2.2. Consuming dates -Services MUST accept dates from clients that use the same `DateLiteral` format (including the `DateKind`, if applicable) that they produce, and SHOULD accept dates using any `DateLiteral` format. - -#### 11.2.3. Compatibility -Services MUST use the same `DateLiteral` format (including the same `DateKind`, if applicable) for all resources of the same type, and SHOULD use the same `DateLiteral` format (and `DateKind`, if applicable) for all resources across the entire service. - -Any change to the `DateLiteral` format produced by the service (including the `DateKind`, if applicable) and any reductions in the `DateLiteral` formats (and `DateKind`, if applicable) accepted by the service MUST be treated as a breaking change. -Any widening of the `DateLiteral` formats accepted by the service is NOT considered a breaking change. - -### 11.3. JSON serialization of dates and times -Round-tripping serialized dates with JSON is a hard problem. -Although ECMAScript supports literals for most built-in types, it does not define a literal format for dates. -The Web has coalesced around the [ECMAScript subset of ISO 8601 date formats (ISO 8601)][iso-8601], but there are situations where this format is not desirable. -For those cases, this document defines a JSON serialization format that can be used to unambiguously represent dates in different formats. -Other serialization formats (such as XML) could be derived from this format. - -#### 11.3.1. The `DateLiteral` format -Dates represented in JSON are serialized using the following grammar. -Informally, a `DateValue` is either an ISO 8601-formatted string or a JSON object containing two properties named `kind` and `value` that together define a point in time. -The following is not a context-free grammar; in particular, the interpretation of `DateValue` depends on the value of `DateKind`, but this minimizes the number of productions required to describe the format. - -``` -DateLiteral: - Iso8601Literal - StructuredDateLiteral - -Iso8601Literal: - A string literal as defined in https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15. Note that the full grammar for ISO 8601 (such as "basic format" without separators) is not supported. - All dates default to UTC unless specified otherwise. - -StructuredDateLiteral: - { DateKindProperty , DateValueProperty } - { DateValueProperty , DateKindProperty } - -DateKindProperty - "kind" : DateKind - -DateKind: - "C" ; see below - "E" ; see below - "I" ; see below - "O" ; see below - "T" ; see below - "U" ; see below - "W" ; see below - "X" ; see below - -DateValueProperty: - "value" : DateValue - -DateValue: - UnsignedInteger ; not defined here - SignedInteger ; not defined here - RealNumber ; not defined here - Iso8601Literal ; as above -``` - -#### 11.3.2. Commentary on date formatting -A `DateLiteral` using the `Iso8601Literal` production is relatively straightforward. -Here is an example of an object with a property named `creationDate` that is set to February 13, 2015, at 1:15 p.m. UTC: - -```json -{ "creationDate" : "2015-02-13T13:15Z" } -``` - -The `StructuredDateLiteral` consists of a `DateKind` and an accompanying `DateValue` whose valid values (and their interpretation) depend on the `DateKind`. The following table describes the valid combinations and their meaning: - -DateKind | DateValue | Colloquial Name & Interpretation | More Info --------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- -C | UnsignedInteger | "CLR"; number of milliseconds since midnight January 1, 0001; negative values are not allowed. *See note below.* | [MSDN][clr-time] -E | SignedInteger | "ECMAScript"; number of milliseconds since midnight, January 1, 1970. | [ECMA International][ecmascript-time] -I | Iso8601Literal | "ISO 8601"; a string limited to the ECMAScript subset. | -O | RealNumber | "OLE Date"; integral part is the number of days since midnight, December 31, 1899, and fractional part is the time within the day (0.5 = midday). | [MSDN][ole-date] -T | SignedInteger | "Ticks"; number of ticks (100-nanosecond intervals) since midnight January 1, 1601. *See note below.* | [MSDN][ticks-time] -U | SignedInteger | "UNIX"; number of seconds since midnight, January 1, 1970. | [MSDN][unix-time] -W | SignedInteger | "Windows"; number of milliseconds since midnight January 1, 1601. *See note below.* | [MSDN][windows-time] -X | RealNumber | "Excel"; as for `O` but the year 1900 is incorrectly treated as a leap year, and day 0 is "January 0 (zero)". | [Microsoft Support][excel-time] - -**Important note for `C` and `W` kinds:** The native CLR and Windows times are represented by 100-nanosecond "tick" values. -To interoperate with ECMAScript clients that have limited precision, _these values MUST be converted to and from milliseconds_ when (de)serialized as a `DateLiteral`. -One millisecond is equivalent to 10,000 ticks. - -**Important note for `T` kind:** This kind preserves the full fidelity of the Windows native time formats (and is trivially convertible to and from the native CLR format) but is incompatible with ECMAScript clients. -Therefore, its use SHOULD be limited to only those scenarios that both require the additional precision and do not need to interoperate with ECMAScript clients. - -Here is the same example of an object with a property named creationDate that is set to February 13, 2015, at 1:15 p.m. UTC, using several formats: - -```json -[ - { "creationDate" : { "kind" : "O", "value" : 42048.55 } }, - { "creationDate" : { "kind" : "E", "value" : 1423862100000 } } -] -``` - -One of the benefits of separating the kind from the value is that once a client knows the kind used by a particular service, it can interpret the value without requiring any additional parsing. -In the common case of the value being a number, this makes coding easier for developers: - -```csharp -// We know this service always gives out ECMAScript-format dates -var date = new Date(serverResponse.someObject.creationDate.value); -``` - -### 11.4. Durations -[Durations][wikipedia-iso8601-durations] need to be serialized in conformance with [ISO 8601][wikipedia-iso8601-durations]. -Durations are "represented by the format `P[n]Y[n]M[n]DT[n]H[n]M[n]S`." -From the standard: -- P is the duration designator (historically called "period") placed at the start of the duration representation. -- Y is the year designator that follows the value for the number of years. -- M is the month designator that follows the value for the number of months. -- W is the week designator that follows the value for the number of weeks. -- D is the day designator that follows the value for the number of days. -- T is the time designator that precedes the time components of the representation. -- H is the hour designator that follows the value for the number of hours. -- M is the minute designator that follows the value for the number of minutes. -- S is the second designator that follows the value for the number of seconds. - -For example, "P3Y6M4DT12H30M5S" represents a duration of "three years, six months, four days, twelve hours, thirty minutes, and five seconds." - -### 11.5. Intervals -[Intervals][wikipedia-iso8601-intervals] are defined as part of [ISO 8601][wikipedia-iso8601-intervals]. -- Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z" -- Start and duration, such as "2007-03-01T13:00:00Z/P1Y2M10DT2H30M" -- Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z" -- Duration only, such as "P1Y2M10DT2H30M", with additional context information - -### 11.6. Repeating intervals -[Repeating Intervals][wikipedia-iso8601-repeatingintervals], as per [ISO 8601][wikipedia-iso8601-repeatingintervals], are: - -> Formed by adding "R[n]/" to the beginning of an interval expression, where R is used as the letter itself and [n] is replaced by the number of repetitions. -Leaving out the value for [n] means an unbounded number of repetitions. - -For example, to repeat the interval of "P1Y2M10DT2H30M" five times starting at "2008-03-01T13:00:00Z", use "R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M." - -## 12. Versioning -**All APIs compliant with the Microsoft REST API Guidelines MUST support explicit versioning.** It's critical that clients can count on services to be stable over time, and it's critical that services can add features and make changes. - -### 12.1. Versioning formats -Services are versioned using a Major.Minor versioning scheme. -Services MAY opt for a "Major" only version scheme in which case the ".0" is implied and all other rules in this section apply. -Two options for specifying the version of a REST API request are supported: -- Embedded in the path of the request URL, at the end of the service root: `https://api.contoso.com/v1.0/products/users` -- As a query string parameter of the URL: `https://api.contoso.com/products/users?api-version=1.0` - -Guidance for choosing between the two options is as follows: - -1. Services co-located behind a DNS endpoint MUST use the same versioning mechanism. -2. In this scenario, a consistent user experience across the endpoint is paramount. The Microsoft REST API Guidelines Working Group recommends that new top-level DNS endpoints are not created without explicit conversations with your organization's leadership team. -3. Services that guarantee the stability of their REST API's URL paths, even through future versions of the API, MAY adopt the query string parameter mechanism. This means the naming and structure of the relationships described in the API cannot evolve after the API ships, even across versions with breaking changes. -4. Services that cannot ensure URL path stability across future versions MUST embed the version in the URL path. - -Certain bedrock services such as Microsoft's Azure Active Directory may be exposed behind multiple endpoints. -Such services MUST support the versioning mechanisms of each endpoint, even if that means supporting multiple versioning mechanisms. - -#### 12.1.1. Group versioning -Group versioning is an OPTIONAL feature that MAY be offered on services using the query string parameter mechanism. -Group versions allow for logical grouping of API endpoints under a common versioning moniker. -This allows developers to look up a single version number and use it across multiple endpoints. -Group version numbers are well known, and services SHOULD reject any unrecognized values. - -Internally, services will take a Group Version and map it to the appropriate Major.Minor version. - -The Group Version format is defined as YYYY-MM-DD, for example 2012-12-07 for December 7, 2012. This Date versioning format applies only to Group Versions and SHOULD NOT be used as an alternative to Major.Minor versioning. - -##### Examples of group versioning - -| Group | Major.Minor | -|:-----------|:------------| -| 2012-12-01 | 1.0 | -| | 1.1 | -| | 1.2 | -| 2013-03-21 | 1.0 | -| | 2.0 | -| | 3.0 | -| | 3.1 | -| | 3.2 | -| | 3.3 | - -Version Format | Example | Interpretation ------------------------------ | ---------------------- | ------------------------------------------ -{groupVersion} | 2013-03-21, 2012-12-01 | 3.3, 1.2 -{majorVersion} | 3 | 3.0 -{majorVersion}.{minorVersion} | 1.2 | 1.2 - -Clients can specify either the group version or the Major.Minor version: - -For example: - -```http -GET http://api.contoso.com/acct1/c1/blob2?api-version=1.0 -``` - -```http -PUT http://api.contoso.com/acct1/c1/b2?api-version=2011-12-07 -``` - -### 12.2. When to version -Services MUST increment their version number in response to any breaking API change. -See the following section for a detailed discussion of what constitutes a breaking change. -Services MAY increment their version number for nonbreaking changes as well, if desired. - -Use a new major version number to signal that support for existing clients will be deprecated in the future. -When introducing a new major version, services MUST provide a clear upgrade path for existing clients and develop a plan for deprecation that is consistent with their business group's policies. -Services SHOULD use a new minor version number for all other changes. - -Online documentation of versioned services MUST indicate the current support status of each previous API version and provide a path to the latest version. - -### 12.3. Definition of a breaking change -Changes to the contract of an API are considered a breaking change. -Changes that impact the backwards compatibility of an API are a breaking change. - -Teams MAY define backwards compatibility as their business needs require. -For example, Azure defines the addition of a new JSON field in a response to be not backwards compatible. -Office 365 has a looser definition of backwards compatibility and allows JSON fields to be added to responses. - -Clear examples of breaking changes: - -1. Removing or renaming APIs or API parameters -2. Changes in behavior for an existing API -3. Changes in Error Codes and Fault Contracts -4. Anything that would violate the [Principle of Least Astonishment][principle-of-least-astonishment] - -Services MUST explicitly define their definition of a breaking change, especially with regard to adding new fields to JSON responses and adding new API arguments with default fields. -Services that are co-located behind a DNS Endpoint with other services MUST be consistent in defining contract extensibility. - -The applicable changes described [in this section of the OData V4 spec][odata-breaking-changes] SHOULD be considered part of the minimum bar that all services MUST consider a breaking change. - -## 13. Long running operations -Long running operations, sometimes called async operations, tend to mean different things to different people. -This section sets forth guidance around different types of long running operations, and describes the wire protocols and best practices for these types of operations. - -1. One or more clients MUST be able to monitor and operate on the same resource at the same time. -2. The state of the system SHOULD be discoverable and testable at all times. Clients SHOULD be able to determine the system state even if the operation tracking resource is no longer active. The act of querying the state of a long running operation should itself leverage principles of the web. i.e. well-defined resources with uniform interface semantics. Clients MAY issue a GET on some resource to determine the state of a long running operation -3. Long running operations SHOULD work for clients looking to "Fire and Forget" and for clients looking to actively monitor and act upon results. -4. Cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a cancelled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. - -### 13.1. Resource based long running operations (RELO) -Resource based modeling is where the status of an operation is encoded in the resource and the wire protocol used is the standard synchronous protocol. -In this model state transitions are well defined and goal states are similarly defined. - -_This is the preferred model for long running operations and should be used wherever possible._ Avoiding the complexity and mechanics of the LRO Wire Protocol makes things simpler for our users and tooling chain. - -An example may be a machine reboot, where the operation itself completes synchronously but the GET operation on the virtual machine resource would have a "state: Rebooting", "state: Running" that could be queried at any time. - -This model MAY integrate Push Notifications. - -While most operations are likely to be POST semantics, in addition to POST semantics, services MAY support PUT semantics via routing to simplify their APIs. -For example, a user that wants to create a database named "db1" could call: - -```http -PUT https://api.contoso.com/v1.0/databases/db1 -``` - -In this scenario the databases segment is processing the PUT operation. - -Services MAY also use the hybrid defined below. - -### 13.2. Stepwise long running operations -A stepwise operation is one that takes a long, and often unpredictable, length of time to complete, and doesn't offer state transition modeled in the resource. -This section outlines the approach that services should use to expose such long running operations. - -Service MAY expose stepwise operations. - -> Stepwise Long Running Operations are sometimes called "Async" operations. -This causes confusion, as it mixes elements of platforms ("Async / await", "promises", "futures") with elements of API operation. -This document uses the term "Stepwise Long Running Operation" or often just "Stepwise Operation" to avoid confusion over the word "Async". - -Services MUST perform as much synchronous validation as practical on stepwise requests. -Services MUST prioritize returning errors in a synchronous way, with the goal of having only "Valid" operations processed using the long running operation wire protocol. - -For an API that's defined as a Stepwise Long Running Operation the service MUST go through the Stepwise Long Running Operation flow even if the operation can be completed immediately. -In other words, APIs must adopt and stick with an LRO pattern and not change patterns based on circumstance. - -#### 13.2.1. PUT -Services MAY enable PUT requests for entity creation. - -```http -PUT https://api.contoso.com/v1.0/databases/db1 -``` - -In this scenario the _databases_ segment is processing the PUT operation. - -```http -HTTP/1.1 202 Accepted -Operation-Location: https://api.contoso.com/v1.0/operations/123 -``` - -For services that need to return a 201 Created here, use the hybrid flow described below. - -The 202 Accepted should return no body. -The 201 Created case should return the body of the target resource. - -#### 13.2.2. POST -Services MAY enable POST requests for entity creation. - -```http -POST https://api.contoso.com/v1.0/databases/ - -{ - "fileName": "someFile.db", - "color": "red" -} -``` - -```http -HTTP/1.1 202 Accepted -Operation-Location: https://api.contoso.com/v1.0/operations/123 -``` - -#### 13.2.3. POST, hybrid model -Services MAY respond synchronously to POST requests to collections that create a resource even if the resources aren't fully created when the response is generated. -In order to use this pattern, the response MUST include a representation of the incomplete resource and an indication that it is incomplete. - -For example: - -```http -POST https://api.contoso.com/v1.0/databases/ HTTP/1.1 -Host: api.contoso.com -Content-Type: application/json -Accept: application/json - -{ - "fileName": "someFile.db", - "color": "red" -} -``` - -Service response says the database has been created, but indicates the request is not completed by including the Operation-Location header. -In this case the status property in the response payload also indicates the operation has not fully completed. - -```http -HTTP/1.1 201 Created -Location: https://api.contoso.com/v1.0/databases/db1 -Operation-Location: https://api.contoso.com/v1.0/operations/123 - -{ - "databaseName": "db1", - "color": "red", - "Status": "Provisioning", - [ … other fields for "database" …] -} -``` - -#### 13.2.4. Operations resource -Services MAY provide a "/operations" resource at the tenant level. - -Services that provide the "/operations" resource MUST provide GET semantics. -GET MUST enumerate the set of operations, following standard pagination, sorting, and filtering semantics. -The default sort order for this operation MUST be: - -Primary Sort | Secondary Sort ----------------------- | ----------------------- -Not Started Operations | Operation Creation Time -Running Operations | Operation Creation Time -Completed Operations | Operation Creation Time - -Note that "Completed Operations" is a goal state (see below), and may actually be any of several different states such as "successful", "cancelled", "failed" and so forth. - -#### 13.2.5. Operation resource -An operation is a user addressable resource that tracks a stepwise long running operation. -Operations MUST support GET semantics. -The GET operation against an operation MUST return: - -1. The operation resource, it's state, and any extended state relevant to the particular API. -2. 200 OK as the response code. - -Services MAY support operation cancellation by exposing DELETE on the operation. -If supported DELETE operations MUST be idempotent. - -> Note: From an API design perspective, cancellation does not explicitly mean rollback. -On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. -Following a cancelled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. - -Services that do not support operation cancellation MUST return a 405 Method Not Allowed in the event of a DELETE. - -Operations MUST support the following states: - -1. NotStarted -2. Running -3. Succeeded. Terminal State. -4. Failed. Terminal State. - -Services MAY add additional states, such as "Cancelled" or "Partially Completed". Services that support cancellation MUST sufficiently describe their cancellation such that the state of the system can be accurately determined, and any compensating actions may be run. - -Services that support additional states should consider this list of canonical names and avoid creating new names if possible: Cancelling, Cancelled, Aborting, Aborted, Tombstone, Deleting, Deleted. - -An operation MUST contain, and provide in the GET response, the following information: - -1. The timestamp when the operation was created. -2. A timestamp for when the current state was entered. -3. The operation state (notstarted / running / completed). - -Services MAY add additional, API specific, fields into the operation. -The operation status JSON returned looks like: - -```json -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "lastActionDateTime": "2015-06-19T12-01-03.45Z", - "status": "notstarted | running | succeeded | failed" -} -``` - -##### Percent complete -Sometimes it is impossible for services to know with any accuracy when an operation will complete. -Which makes using the Retry-After header problematic. -In that case, services MAY include, in the operationStatus JSON, a percent complete field. - -```json -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "percentComplete": "50", - "status": "running" -} -``` - -In this example the server has indicated to the client that the long running operation is 50% complete. - -##### Target resource location -For operations that result in, or manipulate, a resource the service MUST include the target resource location in the status upon operation completion. - -```json -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "lastActionDateTime": "2015-06-19T12-06-03.0024Z", - "status": "succeeded", - "resourceLocation": "https://api.contoso.com/v1.0/databases/db1" -} -``` - -#### 13.2.6. Operation tombstones -Services MAY choose to support tombstoned operations. -Services MAY choose to delete tombstones after a service defined period of time. - -#### 13.2.7. The typical flow, polling -- Client invokes a stepwise operation by invoking an action using POST -- The server MUST indicate the request has been started by responding with a 202 Accepted status code. The response SHOULD include the location header containing a URL that the client should poll for the results after waiting the number of seconds specified in the Retry-After header. -- Client polls the location until receiving a 200 response with a terminal operation state. - -##### Example of the typical flow, polling -Client invokes the restart action: - -```http -POST https://api.contoso.com/v1.0/databases HTTP/1.1 -Accept: application/json - -{ - "fromFile": "myFile.db", - "color": "red" -} -``` - -The server response indicates the request has been created. - -```http -HTTP/1.1 202 Accepted -Operation-Location: https://api.contoso.com/v1.0/operations/123 -``` - -Client waits for a period of time then invokes another request to try to get the operation status. - -```http -GET https://api.contoso.com/v1.0/operations/123 -Accept: application/json -``` - -Server responds that results are still not ready and optionally provides a recommendation to wait 30 seconds. - -```http -HTTP/1.1 200 OK -Retry-After: 30 - -{ - "createdDateTime": "2015-06-19T12-01-03.4Z", - "status": "running" -} -``` - -Client waits the recommended 30 seconds and then invokes another request to get the results of the operation. - -```http -GET https://api.contoso.com/v1.0/operations/123 -Accept: application/json -``` - -Server responds with a "status:succeeded" operation that includes the resource location. - -```http -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "lastActionDateTime": "2015-06-19T12-06-03.0024Z", - "status": "succeeded", - "resourceLocation": "https://api.contoso.com/v1.0/databases/db1" -} -``` - -#### 13.2.8. The typical flow, push notifications -1. Client invokes a long running operation by invoking an action using POST. The client has a push notification already setup on the parent resource. -2. The service indicates the request has been started by responding with a 202 Accepted status code. The client ignores everything else. -3. Upon completion of the overall operation the service pushes a notification via the subscription on the parent resource. -4. The client retrieves the operation result via the resource URL. - -##### Example of the typical flow, push notifications existing subscription -Client invokes the backup action. -The client already has a push notification subscription setup for db1. - -```http -POST https://api.contoso.com/v1.0/databases/db1?backup HTTP/1.1 -Accept: application/json -``` - -The server response indicates the request has been accepted. - -```http -HTTP/1.1 202 Accepted -Operation-Location: https://api.contoso.com/v1.0/operations/123 -``` - -The caller ignores all the headers in the return. - -The target URL receives a push notification when the operation is complete. - -```http -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "value": [ - { - "subscriptionId": "1234-5678-1111-2222", - "context": "subscription context that was specified at setup", - "resourceUrl": "https://api.contoso.com/v1.0/databases/db1", - "userId" : "contoso.com/user@contoso.com", - "tenantId" : "contoso.com" - } - ] -} -``` - -#### 13.2.9. Retry-After -In the examples above the Retry-After header indicates the number of seconds that the client should wait before trying to get the result from the URL identified by the location header. - -The HTTP specification allows the Retry-After header to alternatively specify a HTTP date, so clients should be prepared to handle this as well. - -```http -HTTP/1.1 202 Accepted -Operation-Location: http://api.contoso.com/v1.0/operations/123 -Retry-After: 60 -``` - -Note: The use of the HTTP Date is inconsistent with the use of ISO 8601 Date Format used throughout this document, but is explicitly defined by the HTTP standard in [RFC 7231][rfc-7231-7-1-1-1]. Services SHOULD prefer the integer number of seconds (in decimal) format over the HTTP date format. - -### 13.3. Retention policy for operation results -In some situations, the result of a long running operation is not a resource that can be addressed. -For example, if you invoke a long running Action that returns a Boolean (rather than a resource). -In these situations, the Location header points to a place where the Boolean result can be retrieved. - -Which begs the question: "How long should operation results be retained?" - -A recommended minimum retention time is 24 hours. - -Operations SHOULD transition to "tombstone" for an additional period of time prior to being purged from the system. - -## 14. Throttling, Quotas, and Limits -### 14.1. Principles -Services should be as responsive as possible, so as not to block callers. -As a rule of thumb any API call that is expected to take longer than 0.5 seconds in the 99th percentile, should consider using the Long-running Operations pattern for those calls. -Obviously, services cannot guarantee these response times in the face of potentially unlimited load from callers. Services should therefore design and document call request limits for clients, and respond with appropriate, actionable errors and error messages if these limits are exceeded. -Services should respond quickly with an error when they are generally overloaded, rather than simply respond slowly. -Finally, many services will have quotas on calls, perhaps a number of operations per hour or day, usually related to a service plan or price. -When these quotas are exceeded services must also provide immediate, actionable errors. -Quotas and Limits should be scoped to a customer unit: a subscription, a tenant, an application, a plan, or without any other identification a range of ip addresses…as appropriate to the service goals so that the load is properly shared and one unit is not interfering with another. - -### 14.2. Return Codes (429 vs 503) -HTTP specifies two return codes for these scenarios: '429 Too Many Requests' and '503 Service Unavailable'. -Services should use 429 for cases where clients are making too many calls and can fix the situation by changing their call pattern. -Services should respond with 503 in cases where general load or other problems outside the control of the individual callers is responsible for the service becoming slow. -In all cases, services should also provide information suggesting how long the callers should wait before trying in again. -Clients should respect these headers and also implement other transient fault handling techniques. -However, there may be clients that simply retry immediately upon failure, potentially increasing the load on the service. -To handle this, services should design so that returning 429 or 503 is as inexpensive as possible, either by putting in special fastpath code, or ideally by depending on a common frontdoor or load balancer that provides this functionality. - -### 14.3. Retry-After and RateLimit Headers -The Retry-After header is the standard way for responding to clients who are being throttled. -It is also common, but optional, in the case of limits and quotas (but not overall system load) to respond with header describing the limit that was exceeded. -However, services across Microsoft and the industry use a wide range of different headers for this purpose. -We recommend using three headers to describe the limit, the number of calls remaining under the limit, and the time when the limit will reset. -However, other headers may be appropriate for specific types of limits. In all cases these must be documented. - -### 14.4. Service Guidance -Services should choose time windows as appropriate for the SLAs or business objectives. -In the case of Quotas, the Retry-After time and time window may be very long (hours, days, weeks, even months. Services use 429 to indicate the specific caller has made too many calls, and 503 to indicate that the service is load shedding but that it is not the caller’s responsibility. - -#### 14.4.1. Responsiveness -1. Services MUST respond quickly in all circumstances, even when under load. -2. Calls that take longer than 1s to respond in the 99th percentile SHOULD use the Long-Running Operation pattern -3. Calls that take longer than 0.5s to respond in the 99th percentile should strongly consider the LRO pattern -4. Services SHOULD NOT introduce sleeps, pauses, etc. that block callers or are not actionable (“tar-pitting”). - -#### 14.4.2. Rate Limits and Quotas -When a caller has made too many calls - -1. Services MUST return a 429 code -2. Services MUST return a standard error response describing the specifics so that a programmer can make appropriate changes -3. Services MUST return a Retry-After header that indicates how long clients should wait before retrying -4. Services MAY return RateLimit headers that document the limit or quota that has been exceeded -5. Services MAY return RateLimit-Limit: the number of calls the client is allowed to make in a time window -6. Services MAY return RateLimit-Remaining: the number of calls remaining in the time window -7. Services MAY return RateLimit-Reset: the time at which the window resets in UTC epoch seconds -8. Services MAY return other service specific RateLimit headers as appropriate for more detailed information or specific limits or quotas - -#### 14.4.3. Overloaded services -When services are generally overloaded and load shedding - -1. Services MUST Return a 503 code -2. Services MUST Return a standard error response (see 7.10.2) describing the specifics so that a programmer can make appropriate changes -3. Services MUST Return a Retry-After header that indicates how long clients should wait before retrying -4. In the 503 case, the service SHOULD NOT return RateLimit headers - -#### 14.4.4. Example Response - -```http -HTTP/1.1 429 Too Many Requests -Content-Type: application/json -Retry-After: 5 -RateLimit-Limit: 1000 -RateLimit-Remaining: 0 -RateLimit-Reset: 1538152773 -{ - "error": { - "code": "requestLimitExceeded", - "message": "The caller has made too many requests in the time period.", - "details": { - "code": "RateLimit", - "limit": "1000", - "remaining": "0", - "reset": "1538152773", - } - } -} -``` - -### 14.5. Caller Guidance -Callers include all users of the API: tools, portals, other services, not just user clients - -1. Callers MUST wait for a minimum of time indicated in a response with a Retry-After before retrying a request. -2. Callers MAY assume that request is retriable after receiving a response with a Retry-After header without making any changes to the request. -3. Clients SHOULD use shared SDKs and common transient fault libraries to implement the proper behavior - -See: https://docs.microsoft.com/en-us/azure/architecture/best-practices/transient-faults - -### 14.6. Handling callers that ignore Retry-After headers -Ideally, 429 and 503 returns are so low cost that even clients that retry immediately can be handled. -In these cases, if possible the service team should make an effort to contact or fix the client. -If it is a known partner, a bug or incident should be filed. -In extreme cases it may be necessary to use DoS style protections such as blocking the caller. - -## 15. Push notifications via webhooks -### 15.1. Scope -Services MAY implement push notifications via web hooks. -This section addresses the following key scenario: - -> Push notification via HTTP Callbacks, often called Web Hooks, to publicly-addressable servers. - -The approach set forth is chosen due to its simplicity, broad applicability, and low barrier to entry for service subscribers. -It's intended as a minimal set of requirements and as a starting point for additional functionality. - -### 15.2. Principles -The core principles for services that support web hooks are: - -1. Services MUST implement at least a poke/pull model. In the poke/pull model, a notification is sent to a client, and clients then send a request to get the current state or the record of change since their last notification. This approach avoids complexities around message ordering, missed messages, and change sets. Services MAY add more data to provide rich notifications. -2. Services MUST implement the challenge/response protocol for configuring callback URLs. -3. Services SHOULD have a recommended age-out period, with flexibility for services to vary based on scenario. -4. Services SHOULD allow subscriptions that are raising successful notifications to live forever and SHOULD be tolerant of reasonable outage periods. -5. Firehose subscriptions MUST be delivered only over HTTPS. Services SHOULD require other subscription types to be HTTPS. See the "Security" section for more details. - -### 15.3. Types of subscriptions -There are two subscription types, and services MAY implement either, both, or none. -The supported subscription types are: - -1. Firehose subscriptions – a subscription is manually created for the subscribing application, typically in an app registration portal. Notifications of activity that any users have consented to the app receiving are sent to this single subscription. -2. Per-resource subscriptions – the subscribing application uses code to programmatically create a subscription at runtime for some user-specific entity(s). - -Services that support both subscription types SHOULD provide differentiated developer experiences for the two types: - -1. Firehose – Services MUST NOT require developers to create code except to directly verify and respond to notifications. Services MUST provide administrative UI for subscription management. Services SHOULD NOT assume that end users are aware of the subscription, only the subscribing application's functionality. -2. Per-user – Services MUST provide an API for developers to create and manage subscriptions as part of their app as well as verifying and responding to notifications. Services MAY expect end users to be aware of subscriptions and MUST allow end users to revoke subscriptions where they were created directly in response to user actions. - -### 15.4. Call sequences -The call sequence for a firehose subscription MUST follow the diagram below. -It shows manual registration of application and subscription, and then the end user making use of one of the service's APIs. -At this part of the flow, two things MUST be stored: - -1. The service MUST store the end user's act of consent to receiving notifications from this specific application (typically a background usage OAUTH scope.) -2. The subscribing application MUST store the end user's tokens in order to call back for details once notified of changes. - -The final part of the sequence is the notification flow itself. - -Non-normative implementation guidance: A resource in the service changes and the service needs to run the following logic: - -1. Determine the set of users who have access to the resource, and could thus expect apps to receive notifications about it on their behalf. -2. See which of those users have consented to receiving notifications and from which apps. -3. See which apps have registered a firehose subscription. -4. Join 1, 2, 3 to produce the concrete set of notifications that must be sent to apps. - -It should be noted that the act of user consent and the act of setting up a firehose subscription could arrive in either order. -Services SHOULD send notifications with setup processed in either order. - -![Firehose subscription setup][websequencediagram-firehose-subscription-setup] - -For a per-user subscription, app registration is either manual or automated. -The call flow for a per-user subscription MUST follow the diagram below. -It shows the end user making use of one of the service's APIs, and again, the same two things MUST be stored: - -1. The service MUST store the end user's act of consent to receiving notifications from this specific application (typically a background usage OAUTH scope). -2. The subscribing application MUST store the end user's tokens in order to call back for details once notified of changes. - -In this case, the subscription is set up programmatically using the end-user's token from the subscribing application. -The app MUST store the ID of the registered subscription alongside the user tokens. - -Non normative implementation guidance: In the final part of the sequence, when an item of data in the service changes and the service needs to run the following logic: - -1. Find the set of subscriptions that correspond via resource to the data that changed. -2. For subscriptions created under an app+user token, send a notification to the app per subscription with the subscription ID and user id of the subscription-creator. -- For subscriptions created with an app only token, check that the owner of the changed data or any user that has visibility of the changed data has consented to notifications to the application, and if so send a set of notifications per user id to the app per subscription with the subscription ID. - - ![User subscription setup][websequencediagram-user-subscription-setup] - -### 15.5. Verifying subscriptions -When subscriptions change either programmatically or in response to change via administrative UI portals, the subscribing service needs to be protected from malicious or unexpected calls from services pushing potentially large volumes of notification traffic. - -For all subscriptions, whether firehose or per-user, services MUST send a verification request as part of creation or modification via portal UI or API request, before sending any other notifications. - -Verification requests MUST be of the following format as an HTTP/HTTPS POST to the subscription's _notificationUrl_. - -```http -POST https://{notificationUrl}?validationToken={randomString} -ClientState: clientOriginatedOpaqueToken (if provided by client on subscription-creation) -Content-Length: 0 -``` - -For the subscription to be set up, the application MUST respond with 200 OK to this request, with the _validationToken_ value as the sole entity body. -Note that if the _notificationUrl_ contains query parameters, the _validationToken_ parameter must be appended with an `&`. - -If any challenge request does not receive the prescribed response within 5 seconds of sending the request, the service MUST return an error, MUST NOT create the subscription, and MUST NOT send further requests or notifications to _notificationUrl_. - -Services MAY perform additional validations on URL ownership. - -### 15.6. Receiving notifications -Services SHOULD send notifications in response to service data changes that do not include details of the changes themselves, but include enough information for the subscribing application to respond appropriately to the following process: - -1. Applications MUST identify the correct cached OAuth token to use for a callback -2. Applications MAY look up any previous delta token for the relevant scope of change -3. Applications MUST determine the URL to call to perform the relevant query for the new state of the service, which MAY be a delta query. - -Services that are providing notifications that will be relayed to end users MAY choose to add more detail to notification packets in order to reduce incoming call load on their service. - Such services MUST be clear that notifications are not guaranteed to be delivered and may be lossy or out of order. - -Notifications MAY be aggregated and sent in batches. -Applications MUST be prepared to receive multiple events inside a single push notification. - -The service MUST send all Web Hook data notifications as POST requests. - -Services MUST allow for a 30-second timeout for notifications. -If a timeout occurs or the application responds with a 5xx response, then the service SHOULD retry the notification with exponential back-off. -All other responses will be ignored. - -The service MUST NOT follow 301/302 redirect requests. - -#### 15.6.1. Notification payload -The basic format for notification payloads is a list of events, each containing the id of the subscription whose referenced resources have changed, the type of change, the resource that should be consumed to identify the exact details of the change and sufficient identity information to look up the token required to call that resource. - -For a firehose subscription, a concrete example of this may look like: - -```json -{ - "value": [ - { - "subscriptionId": "32b8cbd6174ab18b", - "resource": "https://api.contoso.com/v1.0/users/user@contoso.com/files?$delta", - "userId" : "", - "tenantId" : "" - } - ] -} -``` - -For a per-user subscription, a concrete example of this may look like: - -```json -{ - "value": [ - { - "subscriptionId": "32b8cbd6174ab183", - "clientState": "clientOriginatedOpaqueToken", - "expirationDateTime": "2016-02-04T11:23Z", - "resource": "https://api.contoso.com/v1.0/users/user@contoso.com/files/$delta", - "userId" : "", - "tenantId" : "" - }, - { - "subscriptionId": "97b391179fa22", - "clientState ": "clientOriginatedOpaqueToken", - "expirationDateTime": "2016-02-04T11:23Z", - "resource": "https://api.contoso.com/v1.0/users/user@contoso.com/files/$delta", - "userId" : "", - "tenantId" : "" - } - ] -} -``` - -Following is a detailed description of the JSON payload. - -A notification item consists a top-level object that contains an array of events, each of which identified the subscription due to which this notification is being sent. - -Field | Description ------ | -------------------------------------------------------------------------------------------------- -value | Array of events that have been raised within the subscription’s scope since the last notification. - -Each item of the events array contains the following properties: - -Field | Description ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -subscriptionId | The id of the subscription due to which this notification has been sent.
    Services MUST provide the *subscriptionId* field. -clientState | Services MUST provide the *clientState* field if it was provided at subscription creation time. -expirationDateTime | Services MUST provide the *expirationDateTime* field if the subscription has one. -resource | Services MUST provide the resource field. This URL MUST be considered opaque by the subscribing application. In the case of a richer notification it MAY be subsumed by message content that implicitly contains the resource URL to avoid duplication.
    If a service is providing this data as part of a more detailed data packet, then it need not be duplicated. -userId | Services MUST provide this field for user-scoped resources. In the case of user-scoped resources, the unique identifier for the user should be used.
    In the case of resources shared between a specific set of users, multiple notifications must be sent, passing the unique identifier of each user.
    For tenant-scoped resources, the user id of the subscription should be used. -tenantId | Services that wish to support cross-tenant requests SHOULD provide this field. Services that provide notifications on tenant-scoped data MUST send this field. - -### 15.7. Managing subscriptions programmatically -For per-user subscriptions, an API MUST be provided to create and manage subscriptions. -The API must support at least the operations described here. - -#### 15.7.1. Creating subscriptions -A client creates a subscription by issuing a POST request against the subscriptions resource. -The subscription namespace is client-defined via the POST operation. - -``` -https://api.contoso.com/apiVersion/$subscriptions -``` - -The POST request contains a single subscription object to be created. -That subscription object has the following properties: - -Property Name | Required | Notes ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- -resource | Yes | Resource path to watch. -notificationUrl | Yes | The target web hook URL. -clientState | No | Opaque string passed back to the client on all notifications. Callers may choose to use this to provide tagging mechanisms. - -If the subscription was successfully created, the service MUST respond with the status code 201 CREATED and a body containing at least the following properties: - -Property Name | Required | Notes ------------------- | -------- | ------------------------------------------------------------------------------------------- -id | Yes | Unique ID of the new subscription that can be used later to update/delete the subscription. -expirationDateTime | No | Uses existing Microsoft REST API Guidelines defined time formats. - -Creation of subscriptions SHOULD be idempotent. -The combination of properties scoped to the auth token, provides a uniqueness constraint. - -Below is an example request using a User + Application principal to subscribe to notifications from a file: - -```http -POST https://api.contoso.com/files/v1.0/$subscriptions HTTP 1.1 -Authorization: Bearer {UserPrincipalBearerToken} - -{ - "resource": "http://api.service.com/v1.0/files/file1.txt", - "notificationUrl": "https://contoso.com/myCallbacks", - "clientState": "clientOriginatedOpaqueToken" -} -``` - -The service SHOULD respond to such a message with a response format minimally like this: - -```json -{ - "id": "32b8cbd6174ab18b", - "expirationDateTime": "2016-02-04T11:23Z" -} -``` - -Below is an example using an Application-Only principal where the application is watching all files to which it's authorized: - -```http -POST https://api.contoso.com/files/v1.0/$subscriptions HTTP 1.1 -Authorization: Bearer {ApplicationPrincipalBearerToken} - -{ - "resource": "All.Files", - "notificationUrl": "https://contoso.com/myCallbacks", - "clientState": "clientOriginatedOpaqueToken" -} -``` - -The service SHOULD respond to such a message with a response format minimally like this: - -```json -{ - "id": "8cbd6174abb391179", - "expirationDateTime": "2016-02-04T11:23Z" -} -``` - -#### 15.7.2. Updating subscriptions -Services MAY support amending subscriptions. - To update the properties of an existing subscription, clients use PATCH requests providing the ID and the properties that need to change. -Omitted properties will retain their values. -To delete a property, assign a value of JSON null to it. - -As with creation, subscriptions are individually managed. - -The following request changes the notification URL of an existing subscription: - -```http -PATCH https://api.contoso.com/files/v1.0/$subscriptions/{id} HTTP 1.1 -Authorization: Bearer {UserPrincipalBearerToken} - -{ - "notificationUrl": "https://contoso.com/myNewCallback" -} -``` - -If the PATCH request contains a new _notificationUrl_, the server MUST perform validation on it as described above. -If the new URL fails to validate, the service MUST fail the PATCH request and leave the subscription in its previous state. - -The service MUST return an empty body and `204 No Content` to indicate a successful patch. - -The service MUST return an error body and status code if the patch failed. - -The operation MUST succeed or fail atomically. - -#### 15.7.3. Deleting subscriptions -Services MUST support deleting subscriptions. -Existing subscriptions can be deleted by making a DELETE request against the subscription resource: - -```http -DELETE https://api.contoso.com/files/v1.0/$subscriptions/{id} HTTP 1.1 -Authorization: Bearer {UserPrincipalBearerToken} -``` - -As with update, the service MUST return `204 No Content` for a successful delete, or an error body and status code to indicate failure. - -#### 15.7.4. Enumerating subscriptions -To get a list of active subscriptions, clients issue a GET request against the subscriptions resource using a User + Application or Application-Only bearer token: - -```http -GET https://api.contoso.com/files/v1.0/$subscriptions HTTP 1.1 -Authorization: Bearer {UserPrincipalBearerToken} -``` - -The service MUST return a format as below using a User + Application principal bearer token: - -```json -{ - "value": [ - { - "id": "32b8cbd6174ab18b", - "resource": " http://api.contoso.com/v1.0/files/file1.txt", - "notificationUrl": "https://contoso.com/myCallbacks", - "clientState": "clientOriginatedOpaqueToken", - "expirationDateTime": "2016-02-04T11:23Z" - } - ] -} -``` - -An example that may be returned using Application-Only principal bearer token: - -```json -{ - "value": [ - { - "id": "6174ab18bfa22", - "resource": "All.Files ", - "notificationUrl": "https://contoso.com/myCallbacks", - "clientState": "clientOriginatedOpaqueToken", - "expirationDateTime": "2016-02-04T11:23Z" - } - ] -} -``` - -### 15.8. Security -All service URLs must be HTTPS (that is, all inbound calls MUST be HTTPS). Services that deal with Web Hooks MUST accept HTTPS. - -We recommend that services that allow client defined Web Hook Callback URLs SHOULD NOT transmit data over HTTP. -This is because information can be inadvertently exposed via client, network, server logs and other mechanisms. - -However, there are scenarios where the above recommendations cannot be followed due to client endpoint or software limitations. -Consequently, services MAY allow web hook URLs that are HTTP. - -Furthermore, services that allow client defined HTTP web hooks callback URLs SHOULD be compliant with privacy policy specified by engineering leadership. -This will typically include recommending that clients prefer SSL connections and adhere to special precautions to ensure that logs and other service data collection are properly handled. - -For example, services may not want to require developers to generate certificates to onboard. -Services might only enable this on test accounts. - -## 16. Unsupported requests -RESTful API clients MAY request functionality that is currently unsupported. -RESTful APIs MUST respond to valid but unsupported requests consistent with this section. - -### 16.1. Essential guidance -RESTful APIs will often choose to limit functionality that can be performed by clients. -For instance, auditing systems allow records to be created but not modified or deleted. -Similarly, some APIs will expose collections but require or otherwise limit filtering and ordering criteria, or MAY not support client-driven pagination. - -### 16.2. Feature allow list -If a service does not support any of the below API features, then an error response MUST be provided if the feature is requested by a caller. -The features are: -- Key Addressing in a collection, such as: `https://api.contoso.com/v1.0/people/user1@contoso.com` -- Filtering a collection by a property value, such as: `https://api.contoso.com/v1.0/people?$filter=name eq 'david'` -- Filtering a collection by range, such as: `http://api.contoso.com/v1.0/people?$filter=hireDate ge 2014-01-01 and hireDate le 2014-12-31` -- Client-driven pagination via $top and $skip, such as: `http://api.contoso.com/v1.0/people?$top=5&$skip=2` -- Sorting by $orderBy, such as: `https://api.contoso.com/v1.0/people?$orderBy=name desc` -- Providing $delta tokens, such as: `https://api.contoso.com/v1.0/people?$delta` - -#### 16.2.1. Error response -Services MUST provide an error response if a caller requests an unsupported feature found in the feature allow list. -The error response MUST be an HTTP status code from the 4xx series, indicating that the request cannot be fulfilled. -Unless a more specific error status is appropriate for the given request, services SHOULD return "400 Bad Request" and an error payload conforming to the error response guidance provided in the Microsoft REST API Guidelines. -Services SHOULD include enough detail in the response message for a developer to determine exactly what portion of the request is not supported. - -Example: - -```http -GET https://api.contoso.com/v1.0/people?$orderBy=name HTTP/1.1 -Accept: application/json -``` - -```http -HTTP/1.1 400 Bad Request -Content-Type: application/json - -{ - "error": { - "code": "ErrorUnsupportedOrderBy", - "message": "Ordering by name is not supported." - } -} -``` - -## 17. Naming guidelines -### 17.1. Approach -Naming policies should aid developers in discovering functionality without having to constantly refer to documentation. -Use of common patterns and standard conventions greatly aids developers in correctly guessing common property names and meanings. -Services SHOULD use verbose naming patterns and SHOULD NOT use abbreviations other than acronyms that are the dominant mode of expression in the domain being represented by the API, (e.g. Url). - -### 17.2. Casing -- Acronyms SHOULD follow the casing conventions as though they were regular words (e.g. Url). -- All identifiers including namespaces, entityTypes, entitySets, properties, actions, functions and enumeration values SHOULD use lowerCamelCase. -- HTTP headers are the exception and SHOULD use standard HTTP convention of Capitalized-Hyphenated-Terms. - -### 17.3. Names to avoid -Certain names are so overloaded in API domains that they lose all meaning or clash with other common usages in domains that cannot be avoided when using REST APIs, such as OAUTH. -Services SHOULD NOT use the following names: -- Context -- Scope -- Resource - -### 17.4. Forming compound names -- Services SHOULD avoid using articles such as 'a', 'the', 'of' unless needed to convey meaning. - - e.g. names such as aUser, theAccount, countOfBooks SHOULD NOT be used, rather user, account, bookCount SHOULD be preferred. -- Services SHOULD add a type to a property name when not doing so would cause ambiguity about how the data is represented or would cause the service not to use a common property name. -- When adding a type to a property name, services MUST add the type at the end, e.g. createdDateTime. - -### 17.5. Identity properties -- Services MUST use string types for identity properties. -- For OData services, the service MUST use the OData @id property to represent the canonical identifier of the resource. -- Services MAY use the simple 'id' property to represent a local or legacy primary key value for a resource. -- Services SHOULD use the name of the relationship postfixed with 'Id' to represent a foreign key to another resource, e.g. subscriptionId. - - The content of this property SHOULD be the canonical ID of the referenced resource. - -### 17.6. Date and time properties - -- For properties requiring both date and time, services MUST use the suffix 'DateTime'. -- For properties requiring only date information without specifying time, services MUST use the suffix 'Date', e.g. birthDate. -- For properties requiring only time information without specifying date, services MUST use the suffix 'Time', e.g. appointmentStartTime. - -### 17.7. Name properties -- For the overall name of a resource typically shown to users, services MUST use the property name 'displayName'. -- Services MAY use other common naming properties, e.g. givenName, surname, signInName. - -### 17.8. Collections and counts -- Services MUST name collections as plural nouns or plural noun phrases using correct English. -- Services MAY use simplified English for nouns that have plurals not in common verbal usage. - - e.g. schemas MAY be used instead of schemata. -- Services MUST name counts of resources with a noun or noun phrase suffixed with 'Count'. - -### 17.9. Common property names -Where services have a property, whose data matches the names below, the service MUST use the name from this table. -This table will grow as services add terms that will be more commonly used. -Service owners adding such terms SHOULD propose additions to this document. - -| | -|------------- | - attendees | - body | - createdDateTime | - childCount | - children | - contentUrl | - country | - createdBy | - displayName | - errorUrl | - eTag | - event | - expirationDateTime | - givenName | - jobTitle | - kind | - id | - lastModifiedDateTime | - location | - memberOf | - message | - name | - owner | - people | - person | - postalCode | - photo | - preferredLanguage | - properties | - signInName | - surname | - tags | - userPrincipalName | - webUrl | - -## 18. Appendix -### 18.1. Sequence diagram notes -All sequence diagrams in this document are generated using the [WebSequenceDiagrams.com](https://www.websequencediagrams.com/). To generate them, paste the text below into the web tool. - -#### 18.1.1. Push notifications, per user flow - -``` -=== Begin Text === -note over Developer, Automation, App Server: - An App Developer like MovieMaker - Wants to integrate with primary service like Dropbox -end note -note over DB Portal, DB App Registration, DB Notifications, DB Auth, DB Service: The primary service like Dropbox -note over Client: The end users' browser or installed app - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Manual App Registration - - -Developer <--> DB Portal : Login into Portal, App Registration UX -DB Portal -> +DB App Registration: App Name etc. -note over DB App Registration: Confirm Portal Access Token - -DB App Registration -> -DB Portal: App ID -DB Portal <--> App Server: Developer copies App ID - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Manual Notification Registration - -Developer <--> DB Portal: webhook registration UX -DB Portal -> +DB Notifications: Register: App Server webhook URL, Scope, App ID -Note over DB Notifications : Confirm Portal Access Token -DB Notifications -> -DB Portal: notification ID -DB Portal --> App Server : Developer may copy notification ID - - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Client Authorization - -Client -> +App Server : Request access to DB protected information -App Server -> -Client : Redirect to DB Authorization endpoint with authorization request -Client -> +DB Auth : Redirected authorization request -Client <--> DB Auth : Authorization UX -DB Auth -> -Client : Redirect back to App Server with code -Client -> +App Server : Redirect request back to access server with access code -App Server -> +DB Auth : Request tokens with access code -note right of DB Service: Cache that this User ID provided access to App ID -DB Auth -> -App Server : Response with access, refresh, and ID tokens -note right of App Server : Cache tokens by user ID -App Server -> -Client : Return information to client - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Notification Flow - -Client <--> DB Service: Changes to user data - typical via interacting with App Server via Client -DB Service -> App Server : Notification with notification ID and user ID -App Server -> +DB Service : Request changed information with cached access tokens and "since" token -note over DB Service: Confirm User Access Token -DB Service -> -App Server : Response with data and new "since" token -note right of App Server: Update status and cache new "since" token -=== End Text === -``` - -#### 18.1.2. Push notifications, firehose flow - -``` -=== Begin Text === -note over Developer, Automation, App Server: - An App Developer like MovieMaker - Wants to integrate with primary service like Dropbox -end note -note over DB Portal, DB App Registration, DB Notifications, DB Auth, DB Service: The primary service like Dropbox -note over Client: The end users' browser or installed app - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : App Registration - -alt Automated app registration - Developer <--> Automation: Configure - Automation -> +DB App Registration: App Name etc. - note over DB App Registration: Confirm App Access Token - DB App Registration -> -Automation: App ID, App Secret - Automation --> App Server : Embed App ID, App Secret -else Manual app registration - Developer <--> DB Portal : Login into Portal, App Registration UX - DB Portal -> +DB App Registration: App Name etc. - note over DB App Registration: Confirm Portal Access Token - - DB App Registration -> -DB Portal: App ID - DB Portal <--> App Server: Developer copies App ID -end - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Client Authorization - -Client -> +App Server : Request access to DB protected information -App Server -> -Client : Redirect to DB Authorization endpoint with authorization request -Client -> +DB Auth : Redirected authorization request -Client <--> DB Auth : Authorization UX -DB Auth -> -Client : Redirect back to App Server with code -Client -> +App Server : Redirect request back to access server with access code -App Server -> +DB Auth : Request tokens with access code -note right of DB Service: Cache that this User ID provided access to App ID -DB Auth -> -App Server : Response with access, refresh, and ID tokens -note right of App Server : Cache tokens by user ID -App Server -> -Client : Return information to client - - - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Notification Registration - -App Server->+DB Notifications: Register: App server webhook URL, Scope, App ID -note over DB Notifications : Confirm User Access Token -DB Notifications -> -App Server: notification ID -note right of App Server : Cache the Notification ID and User Access Token - - - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Notification Flow - -Client <--> DB Service: Changes to user data - typical via interacting with App Server via Client -DB Service -> App Server : Notification with notification ID and user ID -App Server -> +DB Service : Request changed information with cached access tokens and "since" token -note over DB Service: Confirm User Access Token -DB Service -> -App Server : Response with data and new "since" token -note right of App Server: Update status and cache new "since" token - - - -=== End Text === -``` -[fielding]: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm -[IANA-headers]: https://www.iana.org/assignments/message-headers/message-headers.xhtml -[rfc-2119]: https://tools.ietf.org/html/rfc2119 -[rfc7231-7-1-1-1]: https://tools.ietf.org/html/rfc7231#section-7.1.1.1 -[rfc-7230-3-1-1]: https://tools.ietf.org/html/rfc7230#section-3.1.1 -[rfc-7231]: https://tools.ietf.org/html/rfc7231 -[rest-in-practice]: https://www.amazon.com/REST-Practice-Hypermedia-Systems-Architecture/dp/0596805829/ -[rest-on-wikipedia]: https://en.wikipedia.org/wiki/Representational_state_transfer -[rfc-5789]: https://tools.ietf.org/html/rfc5789 -[rfc-5988]: https://tools.ietf.org/html/rfc5988 -[rfc-3339]: https://tools.ietf.org/html/rfc3339 -[rfc-5322-3-3]: https://tools.ietf.org/html/rfc5322#section-3.3 -[cors-preflight]: https://www.w3.org/TR/cors/#resource-preflight-requests -[rfc-3864]: https://tools.ietf.org/html/rfc3864 -[odata-json-annotations]: https://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Instance_Annotations -[cors]: https://www.w3.org/TR/access-control/ -[cors-user-credentials]: https://www.w3.org/TR/access-control/#user-credentials -[cors-simple-headers]: https://www.w3.org/TR/access-control/#simple-header -[rfc-4627]: https://tools.ietf.org/html/rfc4627 -[iso-8601]: https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 -[clr-time]: https://msdn.microsoft.com/en-us/library/System.DateTime(v=vs.110).aspx -[ecmascript-time]: https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.1 -[ole-date]: https://docs.microsoft.com/en-us/windows/desktop/api/oleauto/nf-oleauto-varianttimetosystemtime -[ticks-time]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx -[unix-time]: https://msdn.microsoft.com/en-us/library/1f4c8f33.aspx -[windows-time]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx -[excel-time]: https://support.microsoft.com/kb/214326?wa=wsignin1.0 -[wikipedia-iso8601-durations]: https://en.wikipedia.org/wiki/ISO_8601#Durations -[wikipedia-iso8601-intervals]: https://en.wikipedia.org/wiki/ISO_8601#Time_intervals -[wikipedia-iso8601-repeatingintervals]: https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals -[principle-of-least-astonishment]: https://en.wikipedia.org/wiki/Principle_of_least_astonishment -[odata-breaking-changes]: https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209 -[websequencediagram-firehose-subscription-setup]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=bm90ZSBvdmVyIERldmVsb3BlciwgQXV0b21hdGlvbiwgQXBwIFNlcnZlcjogCiAgICAgQW4AEAUAJwkgbGlrZSBNb3ZpZU1ha2VyACAGV2FudHMgdG8gaW50ZWdyYXRlIHdpdGggcHJpbWFyeSBzZXJ2aWNlADcGRHJvcGJveAplbmQgbm90ZQoAgQwLQiBQb3J0YWwsIERCAIEJBVJlZ2lzdHIAgRkHREIgTm90aWZpYwCBLAVzACEGdXRoACsFUwBgBjogVGhlAF0eAIF_CkNsaWVudAAtBmVuZCB1c2VycycgYnJvd3NlciBvciBpbnN0YWxsZWQgYXBwCgCBIQwAgiQgAIFABQCBIS8AgQoGIDogTWFudWFsAIFzEQoKCgCDAgo8LS0-AIIqCiA6IExvZ2luIGludG8Agj8JAII1ECBVWCAKACoKLT4gKwCCWBM6AIQGBU5hbWUgZXRjLgCDFQ4AGxJDb25maXJtAIEBCEFjY2VzcyBUb2tlbgoKAIM3EyAtPiAtAINkCQBnBklEAIEMCwCBVQUAhQIMAIR3CmNvcGllcwArCACCIHAAhHMMAIMKDwCDABg6IHdlYmhvb2sgcgCCeg4AgnUSAIVQDToAhXYHZXIAgwgGAIcTBgBECVVSTCwgU2NvcGUAhzIGSUQKTgCGPQwAhhwNIACDBh4AHhEAgxEPbgCBagwAgxwNAIMaDiAAgx0MbWF5IGNvcHkALREAhVtqAIZHB0F1dGhvcml6AIY7BwCGXQctPiArAIEuDVJlcXVlc3QgYQCFOQZ0byBEQiBwcm90ZWN0ZWQgaW5mb3IAiiQGCgCDBQstPiAtAIctCVJlZGlyZWN0ADYHAGwNIGVuZHBvaW50AIoWBmEADw1yAHYGAIEQDACJVAcASwtlZAAYHgCICAgAMAcAcA4AhGoGAE0FAIEdFmJhY2sgdG8AhF8NaXRoIGNvZGUAghoaaQCBagcAgToHAD0JAII-B3MAPgsAglEHAEsFAIIzDgCBXw0Agn8GdG9rZW5zACcSAI0_BXJpZ2h0IG9mAItpDUNhY2hlIHRoYXQgdGhpcyBVc2VyIElEIHByb3ZpZGVkAINNCwCIZgoAggcJAIN7D3Nwb25zAI0_BwCECgYsIHJlZnJlc2gsIGFuZCBJRACBHAcAgQMPAIYADQCBDAcAgUUGYnkAjFkFIElEAIQkG3R1cm4AhF4MIHRvIGMAjR8FAIwRagCJVw1GbG93AIYqCQCMaQgAgmoKaGFuZ2UAj3YFAIFXBWRhdGEgLSB0eXBpY2FsIHZpYQCQDgVyYWN0aW5nAJAPBgCJQQt2aWEAjnsHCgCPNgogAIhDEACKZw0AkFMFAIkBDwCDDAUAgkYWKwBNCwCHWApjAIEyBQCHRg0AhWUHYWNoAIQeDACEfwVhbmQgInNpbmNlIgCFEQYAkSQOAIR3CgCNfwcAhHQFAIpQEACBUgsAhFAcAII8BWFuZCBuZXcAYRQAhFUTOiBVcGRhdGUgc3RhdHUAgSkGAIFDBQAxEwoKCg&s=mscgen -[websequencediagram-user-subscription-setup]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=bm90ZSBvdmVyIERldmVsb3BlciwgQXV0b21hdGlvbiwgQXBwIFNlcnZlcjogCiAgICAgQW4AEAUAJwkgbGlrZSBNb3ZpZU1ha2VyACAGV2FudHMgdG8gaW50ZWdyYXRlIHdpdGggcHJpbWFyeSBzZXJ2aWNlADcGRHJvcGJveAplbmQgbm90ZQoAgQwLQiBQb3J0YWwsIERCAIEJBVJlZ2lzdHIAgRkHREIgTm90aWZpYwCBLAVzACEGdXRoACsFUwBgBjogVGhlAF0eAIF_CkNsaWVudAAtBmVuZCB1c2VycycgYnJvd3NlciBvciBpbnN0YWxsZWQgYXBwCgCBIQwAgiQgAIFABQCBIS8AgQoGIDoAgWwRCgphbHQAgyUIAIEHBiByABQMICAAgxsLPC0tPgCDTws6IENvbmZpZ3VyZQogIACDaAsgLT4gKwCCWBMAegZOYW1lIGV0Yy4AhAgFAIMaDQAfEgBdBXJtAIQ_BUFjY2VzcyBUb2tlAIETBgCDOxIgLT4gLQCBFgxBcHAgSUQAhHwIY3JldACBGxAtPgCFFgsgOiBFbWJlZAAkFGVsc2UgTWFudWFsAIIEJACEbQkgOiBMb2dpbiBpbnRvAIUBCQCBKRFVWACGGAUALQoAgh8mAIIZKwCBCAcAgjoNAIIsHACGLwkAgj8IAIESDgCECAYAh1ELAIdFCmNvcGllcwAuCGVuZACEeGoAhWQHQXV0aG9yaXoAhV8HAIV6By0-ICsAg2ANUmVxdWVzdCBhAIRVBnRvIERCIHByb3RlY3RlZCBpbmZvcgCJQQYKAIQaCy0-IC0AhkoJUmVkaXJlY3QANgcAbA0gZW5kcG9pbnQAiTMGYQAPDXIAdgYAgRAMAIhxBwBLC2VkABgeAIRjCAAwB0EAcQxVWAoASQgAgRwWYmFjayB0bwCFdAwAilwFY29kZQCCGRppAIFpBwCBOQcAPQkAgj0HcwA-CwCCUAcASwUAgjIOAIFeDQCCfgZ0b2tlbnMAJxIAjFsFcmlnaHQgb2YAiwUNQ2FjaGUgdGhhdCB0aGlzIFVzZXIgSUQgcHJvdmlkZWQAg0wLAIU6BwCCBAwAg3oPc3BvbnMAjFsHAIQJBiwgcmVmcmVzaCwgYW5kIElEAIEcBwCBAw8AiDENAIEMBwCBRQZieQCLdQUgSUQAhCMbdHVybgCEXQwgdG8gYwCMOwUKCgCLL2oAjXUMAIwTDwCPNQotPisAjhwQOgCORQdlcgCMVwYAg3YIZWJob29rIFVSTCwgU2NvcGUAkAEGSUQAjwoOAI5rDSAAi2UKAINFBQCLYw0AHBEAgzUOOiBuAIE2DABgCACDCB1oZQCBaQ5JRACDYwUAahIAghB4RmxvdwCJMwkAjE0IAIV0CmhhbmdlAJIcBQCEYQVkYXRhIC0gdHlwaWNhbCB2aWEAkjQFcmFjdGluZwCSNQYAjV8LdmlhAJEhBwoAkVwKIACNfhAAhAsNAJJ5BQCCWQ8AhhYFAIVQFisATQsAimEKYwCBMgUAik8NAIhvB2FjaACHKAwAiAkFYW5kICJzaW5jZSIAiBsGAJNKDgCIAQoAhB0cAIFSCwCHWhwAgjwFYW5kIG5ldwBhFACHXxM6IFVwZGF0ZSBzdGF0dQCBKQYAgUMFADETCgoK&s=mscgen diff --git a/README.md b/README.md index 8c88ec68..731da41c 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,19 @@ - # NOTICE TO READERS - - ## Guidance for Azure service teams -Azure service teams should use companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. - -## Guidance for Microsoft Graph service teams - Microsoft Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. +# Microsoft REST API Guidelines +Thank you for your interest in the Microsoft REST API Guidelines. If you have landed here, you're probably interested in learning about APIs. If so, you are in the right place! +We publish these guidelines here with the aim of fostering dialogue and learning in the API community at large. We further hope that these guidelines might encourage other organizations to create guidelines that are appropriate for them and in turn, if they're able, to publish theirs. -In the process of building many of Microsoft's highest scale services, the Microsoft Graph team found the Microsoft API guidelines tremendously useful as a baseline. However, there are several areas where we need to provide more clarity on how developers should describe their APIs. The companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) is a set of amendments and clarifications for Microsoft Graph that act as further reading. Recognizing that two documents is a lot for a new API designer to absorb, our plan is to follow the approach Azure have taken and roll out guidelines for Microsoft Graph into a single consolidated document. +### Guidance for Azure service teams +Azure service teams should reference the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information, please refer to the [README](./azure/README.md) in the Azure folder. ---- - -## Microsoft REST API Guidelines -The [Microsoft REST API Guidelines](Guidelines.md) are Microsoft's internal company-wide REST API design guidelines. -Teams at Microsoft typically reference this document when setting API design policy. -They may additionally create documents specific to their team, adding further guidance or making adjustments as appropriate to their circumstances. - -We publish these guidelines here with the aim of fostering dialogue and learning in the API community at large. -We further hope that these guidelines may encourage other organizations to create guidelines that are appropriate for them and in turn, if they are able, to publish theirs. +### Guidance for Microsoft Graph service teams +Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provide a refined set of guidance targeted specifically for Microsoft Graph services. [![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) ## Code of Conduct -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +This project adopts the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## This repository -This repository contains a collection of documents and related materials supporting the overall Microsoft REST API Guidelines initiative. To contribute to this repository, please see the [contribution guidelines][contribution-guidance]. +This repository contains a collection of documents and related materials supporting the overall Microsoft REST API Guidelines initiative. To contribute to this repository, see the [contribution guidelines][contribution-guidance]. [contribution-guidance]: CONTRIBUTING.md From 20f2d1891cd91de2b5f52c10a8df1e86739420e6 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 18 Dec 2023 18:26:21 -0500 Subject: [PATCH 627/729] merging with REST --- Guidelines.md | 2482 +------------------------------- README.md | 28 +- graph/Guidelines-deprecated.md | 2448 +++++++++++++++++++++++++++++++ graph/GuidelinesGraph.md | 25 +- graph/articles/nullable.md | 311 ++++ 5 files changed, 2783 insertions(+), 2511 deletions(-) create mode 100644 graph/Guidelines-deprecated.md create mode 100644 graph/articles/nullable.md diff --git a/Guidelines.md b/Guidelines.md index fbbdfa5a..71c9a69d 100644 --- a/Guidelines.md +++ b/Guidelines.md @@ -1,2486 +1,10 @@ > # NOTICE TO READERS +> This document has been deprecated and has been moved to the [Microsoft REST API Guidelines deprecated](./graph/Guidelines-deprecated.md). Please refer to the notes below for the latest guidance. > > ## **Guidance for Azure service teams** -> Azure service teams should use the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. +> Azure service teams should use the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information, see the [README](./azure/README.md) in the Azure folder. > > ## **Guidance for Microsoft Graph service teams** -> Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. +> Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. --- - -# Microsoft REST API Guidelines - -## Microsoft REST API Guidelines Working Group - -Name | Name | Name | ----------------------------- | -------------------------------------- | ---------------------------------------- -Dave Campbell (CTO C+E) | Rick Rashid (CTO ASG) | John Shewchuk (Technical Fellow, TED HQ) -Mark Russinovich (CTO Azure) | Steve Lucco (Technical Fellow, DevDiv) | Murali Krishnaprasad (Azure App Plat) -Rob Howard (ASG) | Peter Torr (OSG) | Chris Mullins (ASG) - -
    -Document editors: John Gossman (C+E), Chris Mullins (ASG), Gareth Jones (ASG), Rob Dolin (C+E), Mark Stafford (C+E)
    -
    - -# Microsoft REST API Guidelines - -## 1. Abstract -The Microsoft REST API Guidelines, as a design principle, encourages application developers to have resources accessible to them via a RESTful HTTP interface. -To provide the smoothest possible experience for developers on platforms following the Microsoft REST API Guidelines, REST APIs SHOULD follow consistent design guidelines to make using them easy and intuitive. - -This document establishes the guidelines Microsoft REST APIs SHOULD follow so RESTful interfaces are developed consistently. - -## 2. Table of contents - - -- [Microsoft REST API Guidelines](#microsoft-rest-api-guidelines) - - [Microsoft REST API Guidelines Working Group](#microsoft-rest-api-guidelines-working-group) -- [Microsoft REST API Guidelines](#microsoft-rest-api-guidelines-1) - - [1. Abstract](#1-abstract) - - [2. Table of contents](#2-table-of-contents) - - [3. Introduction](#3-introduction) - - [3.1. Recommended reading](#31-recommended-reading) - - [4. Interpreting the guidelines](#4-interpreting-the-guidelines) - - [4.1. Application of the guidelines](#41-application-of-the-guidelines) - - [4.2. Guidelines for existing services and versioning of services](#42-guidelines-for-existing-services-and-versioning-of-services) - - [4.3. Requirements language](#43-requirements-language) - - [4.4. License](#44-license) - - [5. Taxonomy](#5-taxonomy) - - [5.1. Errors](#51-errors) - - [5.2. Faults](#52-faults) - - [5.3. Latency](#53-latency) - - [5.4. Time to complete](#54-time-to-complete) - - [5.5. Long running API faults](#55-long-running-api-faults) - - [6. Client guidance](#6-client-guidance) - - [6.1. Ignore rule](#61-ignore-rule) - - [6.2. Variable order rule](#62-variable-order-rule) - - [6.3. Silent fail rule](#63-silent-fail-rule) - - [7. Consistency fundamentals](#7-consistency-fundamentals) - - [7.1. URL structure](#71-url-structure) - - [7.2. URL length](#72-url-length) - - [7.3. Canonical identifier](#73-canonical-identifier) - - [7.4. Supported methods](#74-supported-methods) - - [7.4.1. POST](#741-post) - - [7.4.2. PATCH](#742-patch) - - [7.4.3. Creating resources via PATCH (UPSERT semantics)](#743-creating-resources-via-patch-upsert-semantics) - - [7.4.4. Options and link headers](#744-options-and-link-headers) - - [7.5. Standard request headers](#75-standard-request-headers) - - [7.6. Standard response headers](#76-standard-response-headers) - - [7.7. Custom headers](#77-custom-headers) - - [7.8. Specifying headers as query parameters](#78-specifying-headers-as-query-parameters) - - [7.9. PII parameters](#79-pii-parameters) - - [7.10. Response formats](#710-response-formats) - - [7.10.1. Clients-specified response format](#7101-clients-specified-response-format) - - [7.10.2. Error condition responses](#7102-error-condition-responses) - - [ErrorResponse : Object](#errorresponse--object) - - [Error : Object](#error--object) - - [InnerError : Object](#innererror--object) - - [Examples](#examples) - - [7.11. HTTP Status Codes](#711-http-status-codes) - - [7.12. Client library optional](#712-client-library-optional) - - [8. CORS](#8-cors) - - [8.1. Client guidance](#81-client-guidance) - - [8.1.1. Avoiding preflight](#811-avoiding-preflight) - - [8.2. Service guidance](#82-service-guidance) - - [9. Collections](#9-collections) - - [9.1. Item keys](#91-item-keys) - - [9.2. Serialization](#92-serialization) - - [9.3. Collection URL patterns](#93-collection-url-patterns) - - [9.3.1. Nested collections and properties](#931-nested-collections-and-properties) - - [9.4. Big collections](#94-big-collections) - - [9.5. Changing collections](#95-changing-collections) - - [9.6. Sorting collections](#96-sorting-collections) - - [9.6.1. Interpreting a sorting expression](#961-interpreting-a-sorting-expression) - - [9.7. Filtering](#97-filtering) - - [9.7.1. Filter operations](#971-filter-operations) - - [9.7.2. Operator examples](#972-operator-examples) - - [9.7.3. Operator precedence](#973-operator-precedence) - - [9.8. Pagination](#98-pagination) - - [9.8.1. Continuation tokens](#981-continuation-tokens) - - [9.8.2. Server-driven paging](#982-server-driven-paging) - - [9.8.3. Client-driven paging](#983-client-driven-paging) - - [9.8.4. Additional considerations](#984-additional-considerations) - - [9.9. Compound collection operations](#99-compound-collection-operations) - - [9.10. Empty Results](#910-empty-results) - - [10. Delta queries](#10-delta-queries) - - [10.1. Delta links](#101-delta-links) - - [10.2. Entity representation](#102-entity-representation) - - [10.3. Obtaining a delta link](#103-obtaining-a-delta-link) - - [10.4. Contents of a delta link response](#104-contents-of-a-delta-link-response) - - [10.5. Using a delta link](#105-using-a-delta-link) - - [11. JSON standardizations](#11-json-standardizations) - - [11.1. JSON formatting standardization for primitive types](#111-json-formatting-standardization-for-primitive-types) - - [11.2. Guidelines for dates and times](#112-guidelines-for-dates-and-times) - - [11.2.1. Producing dates](#1121-producing-dates) - - [11.2.2. Consuming dates](#1122-consuming-dates) - - [11.2.3. Compatibility](#1123-compatibility) - - [11.3. JSON serialization of dates and times](#113-json-serialization-of-dates-and-times) - - [11.3.1. The `DateLiteral` format](#1131-the-dateliteral-format) - - [11.3.2. Commentary on date formatting](#1132-commentary-on-date-formatting) - - [11.4. Durations](#114-durations) - - [11.5. Intervals](#115-intervals) - - [11.6. Repeating intervals](#116-repeating-intervals) - - [12. Versioning](#12-versioning) - - [12.1. Versioning formats](#121-versioning-formats) - - [12.1.1. Group versioning](#1211-group-versioning) - - [Examples of group versioning](#examples-of-group-versioning) - - [12.2. When to version](#122-when-to-version) - - [12.3. Definition of a breaking change](#123-definition-of-a-breaking-change) - - [13. Long running operations](#13-long-running-operations) - - [13.1. Resource based long running operations (RELO)](#131-resource-based-long-running-operations-relo) - - [13.2. Stepwise long running operations](#132-stepwise-long-running-operations) - - [13.2.1. PUT](#1321-put) - - [13.2.2. POST](#1322-post) - - [13.2.3. POST, hybrid model](#1323-post-hybrid-model) - - [13.2.4. Operations resource](#1324-operations-resource) - - [13.2.5. Operation resource](#1325-operation-resource) - - [Percent complete](#percent-complete) - - [Target resource location](#target-resource-location) - - [13.2.6. Operation tombstones](#1326-operation-tombstones) - - [13.2.7. The typical flow, polling](#1327-the-typical-flow-polling) - - [Example of the typical flow, polling](#example-of-the-typical-flow-polling) - - [13.2.8. The typical flow, push notifications](#1328-the-typical-flow-push-notifications) - - [Example of the typical flow, push notifications existing subscription](#example-of-the-typical-flow-push-notifications-existing-subscription) - - [13.2.9. Retry-After](#1329-retry-after) - - [13.3. Retention policy for operation results](#133-retention-policy-for-operation-results) - - [14. Throttling, Quotas, and Limits](#14-throttling-quotas-and-limits) - - [14.1. Principles](#141-principles) - - [14.2. Return Codes (429 vs 503)](#142-return-codes-429-vs-503) - - [14.3. Retry-After and RateLimit Headers](#143-retry-after-and-ratelimit-headers) - - [14.4. Service Guidance](#144-service-guidance) - - [14.4.1. Responsiveness](#1441-responsiveness) - - [14.4.2. Rate Limits and Quotas](#1442-rate-limits-and-quotas) - - [14.4.3. Overloaded services](#1443-overloaded-services) - - [14.4.4. Example Response](#1444-example-response) - - [14.5. Caller Guidance](#145-caller-guidance) - - [14.6. Handling callers that ignore Retry-After headers](#146-handling-callers-that-ignore-retry-after-headers) - - [15. Push notifications via webhooks](#15-push-notifications-via-webhooks) - - [15.1. Scope](#151-scope) - - [15.2. Principles](#152-principles) - - [15.3. Types of subscriptions](#153-types-of-subscriptions) - - [15.4. Call sequences](#154-call-sequences) - - [15.5. Verifying subscriptions](#155-verifying-subscriptions) - - [15.6. Receiving notifications](#156-receiving-notifications) - - [15.6.1. Notification payload](#1561-notification-payload) - - [15.7. Managing subscriptions programmatically](#157-managing-subscriptions-programmatically) - - [15.7.1. Creating subscriptions](#1571-creating-subscriptions) - - [15.7.2. Updating subscriptions](#1572-updating-subscriptions) - - [15.7.3. Deleting subscriptions](#1573-deleting-subscriptions) - - [15.7.4. Enumerating subscriptions](#1574-enumerating-subscriptions) - - [15.8. Security](#158-security) - - [16. Unsupported requests](#16-unsupported-requests) - - [16.1. Essential guidance](#161-essential-guidance) - - [16.2. Feature allow list](#162-feature-allow-list) - - [16.2.1. Error response](#1621-error-response) - - [17. Naming guidelines](#17-naming-guidelines) - - [17.1. Approach](#171-approach) - - [17.2. Casing](#172-casing) - - [17.3. Names to avoid](#173-names-to-avoid) - - [17.4. Forming compound names](#174-forming-compound-names) - - [17.5. Identity properties](#175-identity-properties) - - [17.6. Date and time properties](#176-date-and-time-properties) - - [17.7. Name properties](#177-name-properties) - - [17.8. Collections and counts](#178-collections-and-counts) - - [17.9. Common property names](#179-common-property-names) - - [18. Appendix](#18-appendix) - - [18.1. Sequence diagram notes](#181-sequence-diagram-notes) - - [18.1.1. Push notifications, per user flow](#1811-push-notifications-per-user-flow) - - [18.1.2. Push notifications, firehose flow](#1812-push-notifications-firehose-flow) - - - -## 3. Introduction -Developers access most Microsoft Cloud Platform resources via HTTP interfaces. -Although each service typically provides language-specific frameworks to wrap their APIs, all of their operations eventually boil down to HTTP requests. -Microsoft must support a wide range of clients and services and cannot rely on rich frameworks being available for every development environment. -Thus, a goal of these guidelines is to ensure Microsoft REST APIs can be easily and consistently consumed by any client with basic HTTP support. - -To provide the smoothest possible experience for developers, it's important to have these APIs follow consistent design guidelines, thus making using them easy and intuitive. -This document establishes the guidelines to be followed by Microsoft REST API developers for developing such APIs consistently. - -The benefits of consistency accrue in aggregate as well; consistency allows teams to leverage common code, patterns, documentation and design decisions. - -These guidelines aim to achieve the following: -- Define consistent practices and patterns for all API endpoints across Microsoft. -- Adhere as closely as possible to accepted REST/HTTP best practices in the industry at-large. [\*] -- Make accessing Microsoft Services via REST interfaces easy for all application developers. -- Allow service developers to leverage the prior work of other services to implement, test and document REST endpoints defined consistently. -- Allow for partners (e.g., non-Microsoft entities) to use these guidelines for their own REST endpoint design. - -[\*] Note: The guidelines are designed to align with building services which comply with the REST architectural style, though they do not address or require building services that follow the REST constraints. -The term "REST" is used throughout this document to mean services that are in the spirit of REST rather than adhering to REST by the book.* - -### 3.1. Recommended reading -Understanding the philosophy behind the REST Architectural Style is recommended for developing good HTTP-based services. -If you are new to RESTful design, here are some good resources: - -[REST on Wikipedia][rest-on-wikipedia] -- Overview of common definitions and core ideas behind REST. - -[REST Dissertation][fielding] -- The chapter on REST in Roy Fielding's dissertation on Network Architecture, "Architectural Styles and the Design of Network-based Software Architectures" - -[RFC 7231][rfc-7231] -- Defines the specification for HTTP/1.1 semantics, and is considered the authoritative resource. - -[REST in Practice][rest-in-practice] -- Book on the fundamentals of REST. - -## 4. Interpreting the guidelines -### 4.1. Application of the guidelines -These guidelines are applicable to any REST API exposed publicly by Microsoft or any partner service. -Private or internal APIs SHOULD also try to follow these guidelines because internal services tend to eventually be exposed publicly. - Consistency is valuable to not only external customers but also internal service consumers, and these guidelines offer best practices useful for any service. - -There are legitimate reasons for exemption from these guidelines. -Obviously, a REST service that implements or must interoperate with some externally defined REST API must be compatible with that API and not necessarily these guidelines. -Some services MAY also have special performance needs that require a different format, such as a binary protocol. - -### 4.2. Guidelines for existing services and versioning of services -We do not recommend making a breaking change to a service that predates these guidelines simply for the sake of compliance. -The service SHOULD try to become compliant at the next version release when compatibility is being broken anyway. -When a service adds a new API, that API SHOULD be consistent with the other APIs of the same version. -So if a service was written against version 1.0 of the guidelines, new APIs added incrementally to the service SHOULD also follow version 1.0. The service can then upgrade to align with the latest version of the guidelines at the service's next major release. - -### 4.3. Requirements language -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119][rfc-2119]. - -### 4.4. License - -This work is licensed under the Creative Commons Attribution 4.0 International License. -To view a copy of this license, visit https://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. - -## 5. Taxonomy -As part of onboarding to Microsoft REST API Guidelines, services MUST comply with the taxonomy defined below. - -### 5.1. Errors -Errors, or more specifically Service Errors, are defined as a client passing invalid data to the service and the service _correctly_ rejecting that data. -Examples include invalid credentials, incorrect parameters, unknown version IDs, or similar. -These are generally "4xx" HTTP error codes and are the result of a client passing incorrect or invalid data. - -Errors do _not_ contribute to overall API availability. - -### 5.2. Faults -Faults, or more specifically Service Faults, are defined as the service failing to correctly return in response to a valid client request. -These are generally "5xx" HTTP error codes. - -Faults _do_ contribute to the overall API availability. - -Calls that fail due to rate limiting or quota failures MUST NOT count as faults. -Calls that fail as the result of a service fast-failing requests (often for its own protection) do count as faults. - -### 5.3. Latency -Latency is defined as how long a particular API call takes to complete, measured as closely to the client as possible. -This metric applies to both synchronous and asynchronous APIs in the same way. -For long running calls, the latency is measured on the initial request and measures how long that call (not the overall operation) takes to complete. - -### 5.4. Time to complete -Services that expose long operations MUST track "Time to Complete" metrics around those operations. - -### 5.5. Long running API faults -For a Long Running API, it's possible for both the initial request which begins the operation and the request which retrieves the results to technically work (each passing back a 200) but for the underlying operation to have failed. -Long Running faults MUST roll up as faults into the overall Availability metrics. - -## 6. Client guidance -To ensure the best possible experience for clients talking to a REST service, clients SHOULD adhere to the following best practices: - -### 6.1. Ignore rule -For loosely coupled clients where the exact shape of the data is not known before the call, if the server returns something the client wasn't expecting, the client MUST safely ignore it. - -Some services MAY add fields to responses without changing versions numbers. -Services that do so MUST make this clear in their documentation and clients MUST ignore unknown fields. - -### 6.2. Variable order rule -Clients MUST NOT rely on the order in which data appears in JSON service responses. -For example, clients SHOULD be resilient to the reordering of fields within a JSON object. -When supported by the service, clients MAY request that data be returned in a specific order. -For example, services MAY support the use of the _$orderBy_ querystring parameter to specify the order of elements within a JSON array. -Services MAY also explicitly specify the ordering of some elements as part of the service contract. -For example, a service MAY always return a JSON object's "type" information as the first field in an object to simplify response parsing on the client. -Clients MAY rely on ordering behavior explicitly identified by the service. - -### 6.3. Silent fail rule -Clients requesting OPTIONAL server functionality (such as optional headers) MUST be resilient to the server ignoring that particular functionality. - -## 7. Consistency fundamentals -### 7.1. URL structure -Humans SHOULD be able to easily read and construct URLs. - -This facilitates discovery and eases adoption on platforms without a well-supported client library. - -An example of a well-structured URL is: - -``` -https://api.contoso.com/v1.0/people/jdoe@contoso.com/inbox -``` - -An example URL that is not friendly is: - -``` -https://api.contoso.com/EWS/OData/Users('jdoe@microsoft.com')/Folders('AAMkADdiYzI1MjUzLTk4MjQtNDQ1Yy05YjJkLWNlMzMzYmIzNTY0MwAuAAAAAACzMsPHYH6HQoSwfdpDx-2bAQCXhUk6PC1dS7AERFluCgBfAAABo58UAAA=') -``` - -A frequent pattern that comes up is the use of URLs as values. -Services MAY use URLs as values. -For example, the following is acceptable: - -``` -https://api.contoso.com/v1.0/items?url=https://resources.contoso.com/shoes/fancy -``` - -### 7.2. URL length -The HTTP 1.1 message format, defined in RFC 7230, in section [3.1.1][rfc-7230-3-1-1], defines no length limit on the Request Line, which includes the target URL. -From the RFC: - -> HTTP does not place a predefined limit on the length of a - request-line. [...] A server that receives a request-target longer than any URI it wishes to parse MUST respond - with a 414 (URI Too Long) status code. - -Services that can generate URLs longer than 2,083 characters MUST make accommodations for the clients they wish to support. -Here are some sources for determining what target clients support: - - * [https://stackoverflow.com/a/417184](https://stackoverflow.com/a/417184) - * [https://blogs.msdn.microsoft.com/ieinternals/2014/08/13/url-length-limits/](https://blogs.msdn.microsoft.com/ieinternals/2014/08/13/url-length-limits/) - -Also note that some technology stacks have hard and adjustable URL limits, so keep this in mind as you design your services. - -### 7.3. Canonical identifier -In addition to friendly URLs, resources that can be moved or be renamed SHOULD expose a URL that contains a unique stable identifier. -It MAY be necessary to interact with the service to obtain a stable URL from the friendly name for the resource, as in the case of the "/my" shortcut used by some services. - -The stable identifier is not required to be a GUID. - -An example of a URL containing a canonical identifier is: - -``` -https://api.contoso.com/v1.0/people/7011042402/inbox -``` - -### 7.4. Supported methods -Operations MUST use the proper HTTP methods whenever possible, and operation idempotency MUST be respected. -HTTP methods are frequently referred to as the HTTP verbs. -The terms are synonymous in this context, however the HTTP specification uses the term method. - -Below is a list of methods that Microsoft REST services SHOULD support. -Not all resources will support all methods, but all resources using the methods below MUST conform to their usage. - -Method | Description | Is Idempotent -------- | -------------------------------------------------------------------------------------------------------------------------- | ------------- -GET | Return the current value of an object | True -PUT | Replace an object, or create a named object, when applicable | True -DELETE | Delete an object | True -POST | Create a new object based on the data provided, or submit a command | False -HEAD | Return metadata of an object for a GET response. Resources that support the GET method MAY support the HEAD method as well | True -PATCH | Apply a partial update to an object | False -OPTIONS | Get information about a request; see below for details. | True - -Table 1 - -#### 7.4.1. POST -POST operations SHOULD support the Location response header to specify the location of any created resource that was not explicitly named, via the Location header. - -As an example, imagine a service that allows creation of hosted servers, which will be named by the service: - -```http -POST http://api.contoso.com/account1/servers -``` - -The response would be something like: - -```http -201 Created -Location: http://api.contoso.com/account1/servers/server321 -``` - -Where "server321" is the service-allocated server name. - -Services MAY also return the full metadata for the created item in the response. - -#### 7.4.2. PATCH -PATCH has been standardized by IETF as the method to be used for updating an existing object incrementally (see [RFC 5789][rfc-5789]). -Microsoft REST API Guidelines compliant APIs SHOULD support PATCH. - -#### 7.4.3. Creating resources via PATCH (UPSERT semantics) -Services that allow callers to specify key values on create SHOULD support UPSERT semantics, and those that do MUST support creating resources using PATCH. -Because PUT is defined as a complete replacement of the content, it is dangerous for clients to use PUT to modify data. -Clients that do not understand (and hence ignore) properties on a resource are not likely to provide them on a PUT when trying to update a resource, hence such properties could be inadvertently removed. -Services MAY optionally support PUT to update existing resources, but if they do they MUST use replacement semantics (that is, after the PUT, the resource's properties MUST match what was provided in the request, including deleting any server properties that were not provided). - -Under UPSERT semantics, a PATCH call to a nonexistent resource is handled by the server as a "create", and a PATCH call to an existing resource is handled as an "update". To ensure that an update request is not treated as a create or vice versa, the client MAY specify precondition HTTP headers in the request. -The service MUST NOT treat a PATCH request as an insert if it contains an If-Match header and MUST NOT treat a PATCH request as an update if it contains an If-None-Match header with a value of "*". - -If a service does not support UPSERT, then a PATCH call against a resource that does not exist MUST result in an HTTP "409 Conflict" error. - -#### 7.4.4. Options and link headers -OPTIONS allows a client to retrieve information about a resource, at a minimum by returning the Allow header denoting the valid methods for this resource. - -In addition, services SHOULD include a Link header (see [RFC 5988][rfc-5988]) to point to documentation for the resource in question: - -```http -Link: <{help}>; rel="help" -``` - -Where {help} is the URL to a documentation resource. - -For examples on use of OPTIONS, see [preflighting CORS cross-domain calls][cors-preflight]. - -### 7.5. Standard request headers -The table of request headers below SHOULD be used by Microsoft REST API Guidelines services. -Using these headers is not mandated, but if used they MUST be used consistently. - -All header values MUST follow the syntax rules set forth in the specification where the header field is defined. -Many HTTP headers are defined in [RFC7231][rfc-7231], however a complete list of approved headers can be found in the [IANA Header Registry][IANA-headers]." - -Header | Type | Description ---------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Authorization | String | Authorization header for the request -Date | Date | Timestamp of the request, based on the client's clock, in [RFC 5322][rfc-5322-3-3] date and time format. The server SHOULD NOT make any assumptions about the accuracy of the client's clock. This header MAY be included in the request, but MUST be in this format when supplied. Greenwich Mean Time (GMT) MUST be used as the time zone reference for this header when it is provided. For example: `Wed, 24 Aug 2016 18:41:30 GMT`. Note that GMT is exactly equal to UTC (Coordinated Universal Time) for this purpose. -Accept | Content type | The requested content type for the response such as:
    • application/xml
    • text/xml
    • application/json
    • text/javascript (for JSONP)
    Per the HTTP guidelines, this is just a hint and responses MAY have a different content type, such as a blob fetch where a successful response will just be the blob stream as the payload. For services following OData, the preference order specified in OData SHOULD be followed. -Accept-Encoding | Gzip, deflate | REST endpoints SHOULD support GZIP and DEFLATE encoding, when applicable. For very large resources, services MAY ignore and return uncompressed data. -Accept-Language | "en", "es", etc. | Specifies the preferred language for the response. Services are not required to support this, but if a service supports localization it MUST do so through the Accept-Language header. -Accept-Charset | Charset type like "UTF-8" | Default is UTF-8, but services SHOULD be able to handle ISO-8859-1. -Content-Type | Content type | Mime type of request body (PUT/POST/PATCH) -Prefer | return=minimal, return=representation | If the return=minimal preference is specified, services SHOULD return an empty body in response to a successful insert or update. If return=representation is specified, services SHOULD return the created or updated resource in the response. Services SHOULD support this header if they have scenarios where clients would sometimes benefit from responses, but sometimes the response would impose too much of a hit on bandwidth. -If-Match, If-None-Match, If-Range | String | Services that support updates to resources using optimistic concurrency control MUST support the If-Match header to do so. Services MAY also use other headers related to ETags as long as they follow the HTTP specification. - -### 7.6. Standard response headers -Services SHOULD return the following response headers, except where noted in the "required" column. - -Response Header | Required | Description ------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Date | All responses | Timestamp the response was processed, based on the server's clock, in [RFC 5322][rfc-5322-3-3] date and time format. This header MUST be included in the response. Greenwich Mean Time (GMT) MUST be used as the time zone reference for this header. For example: `Wed, 24 Aug 2016 18:41:30 GMT`. Note that GMT is exactly equal to UTC (Coordinated Universal Time) for this purpose. -Content-Type | All responses | The content type -Content-Encoding | All responses | GZIP or DEFLATE, as appropriate -Preference-Applied | When specified in request | Whether a preference indicated in the Prefer request header was applied -ETag | When the requested resource has an entity tag | The ETag response-header field provides the current value of the entity tag for the requested variant. Used with If-Match, If-None-Match and If-Range to implement optimistic concurrency control. - -### 7.7. Custom headers -Custom headers MUST NOT be required for the basic operation of a given API. - -Some of the guidelines in this document prescribe the use of nonstandard HTTP headers. -In addition, some services MAY need to add extra functionality, which is exposed via HTTP headers. -The following guidelines help maintain consistency across usage of custom headers. - -Headers that are not standard HTTP headers MUST have one of two formats: - -1. A generic format for headers that are registered as "provisional" with IANA ([RFC 3864][rfc-3864]) -2. A scoped format for headers that are too usage-specific for registration - -These two formats are described below. - -### 7.8. Specifying headers as query parameters -Some headers pose challenges for some scenarios such as AJAX clients, especially when making cross-domain calls where adding headers MAY not be supported. -As such, some headers MAY be accepted as Query Parameters in addition to headers, with the same naming as the header: - -Not all headers make sense as query parameters, including most standard HTTP headers. - -The criteria for considering when to accept headers as parameters are: - -1. Any custom headers MUST be also accepted as parameters. -2. Required standard headers MAY be accepted as parameters. -3. Required headers with security sensitivity (e.g., Authorization header) MIGHT NOT be appropriate as parameters; the service owner SHOULD evaluate these on a case-by-case basis. - -The one exception to this rule is the Accept header. -It's common practice to use a scheme with simple names instead of the full functionality described in the HTTP specification for Accept. - -### 7.9. PII parameters -Consistent with their organization's privacy policy, clients SHOULD NOT transmit personally identifiable information (PII) parameters in the URL (as part of path or query string) because this information can be inadvertently exposed via client, network, and server logs and other mechanisms. - -Consequently, a service SHOULD accept PII parameters transmitted as headers. - -However, there are many scenarios where the above recommendations cannot be followed due to client or software limitations. -To address these limitations, services SHOULD also accept these PII parameters as part of the URL consistent with the rest of these guidelines. - -Services that accept PII parameters -- whether in the URL or as headers -- SHOULD be compliant with privacy policy specified by their organization's engineering leadership. -This will typically include recommending that clients prefer headers for transmission and implementations adhere to special precautions to ensure that logs and other service data collection are properly handled. - -### 7.10. Response formats -For organizations to have a successful platform, they must serve data in formats developers are accustomed to using, and in consistent ways that allow developers to handle responses with common code. - -Web-based communication, especially when a mobile or other low-bandwidth client is involved, has moved quickly in the direction of JSON for a variety of reasons, including its tendency to be lighter weight and its ease of consumption with JavaScript-based clients. - -JSON property names SHOULD be camelCased. - -Services SHOULD provide JSON as the default encoding. - -#### 7.10.1. Clients-specified response format -In HTTP, response format SHOULD be requested by the client using the Accept header. -This is a hint, and the server MAY ignore it if it chooses to, even if this isn't typical of well-behaved servers. -Clients MAY send multiple Accept headers and the service MAY choose one of them. - -The default response format (no Accept header provided) SHOULD be application/json, and all services MUST support application/json. - -Accept Header | Response type | Notes ----------------- | ---------------------------------- | ------------------------------------------- -application/json | Payload SHOULD be returned as JSON | Also accept text/javascript for JSONP cases - -```http -GET https://api.contoso.com/v1.0/products/user -Accept: application/json -``` - -#### 7.10.2. Error condition responses -For non-success conditions, developers SHOULD be able to write one piece of code that handles errors consistently across different Microsoft REST API Guidelines services. -This allows building of simple and reliable infrastructure to handle exceptions as a separate flow from successful responses. -The following is based on the OData v4 JSON spec. -However, it is very generic and does not require specific OData constructs. -APIs SHOULD use this format even if they are not using other OData constructs. - -The error response MUST be a single JSON object. -This object MUST have a name/value pair named "error". The value MUST be a JSON object. - -This object MUST contain name/value pairs with the names "code" and "message", and it MAY contain name/value pairs with the names "target", "details" and "innererror." - -The value for the "code" name/value pair is a language-independent string. -Its value is a service-defined error code that SHOULD be human-readable. -This code serves as a more specific indicator of the error than the HTTP error code specified in the response. -Services SHOULD have a relatively small number (about 20) of possible values for "code", and all clients MUST be capable of handling all of them. -Most services will require a much larger number of more specific error codes, which are not interesting to all clients. -These error codes SHOULD be exposed in the "innererror" name/value pair as described below. -Introducing a new value for "code" that is visible to existing clients is a breaking change and requires a version increase. -Services can avoid breaking changes by adding new error codes to "innererror" instead. - -The value for the "message" name/value pair MUST be a human-readable representation of the error. -It is intended as an aid to developers and is not suitable for exposure to end users. -Services wanting to expose a suitable message for end users MUST do so through an [annotation][odata-json-annotations] or custom property. -Services SHOULD NOT localize "message" for the end user, because doing so might make the value unreadable to the app developer who may be logging the value, as well as make the value less searchable on the Internet. - -The value for the "target" name/value pair is the target of the particular error (e.g., the name of the property in error). - -The value for the "details" name/value pair MUST be an array of JSON objects that MUST contain name/value pairs for "code" and "message", and MAY contain a name/value pair for "target", as described above. -The objects in the "details" array usually represent distinct, related errors that occurred during the request. -See example below. - -The value for the "innererror" name/value pair MUST be an object. -The contents of this object are service-defined. -Services wanting to return more specific errors than the root-level code MUST do so by including a name/value pair for "code" and a nested "innererror". Each nested "innererror" object represents a higher level of detail than its parent. -When evaluating errors, clients MUST traverse through all of the nested "innererrors" and choose the deepest one that they understand. -This scheme allows services to introduce new error codes anywhere in the hierarchy without breaking backwards compatibility, so long as old error codes still appear. -The service MAY return different levels of depth and detail to different callers. -For example, in development environments, the deepest "innererror" MAY contain internal information that can help debug the service. -To guard against potential security concerns around information disclosure, services SHOULD take care not to expose too much detail unintentionally. -Error objects MAY also include custom server-defined name/value pairs that MAY be specific to the code. -Error types with custom server-defined properties SHOULD be declared in the service's metadata document. -See example below. - -Error responses MAY contain [annotations][odata-json-annotations] in any of their JSON objects. - -We recommend that for any transient errors that may be retried, services SHOULD include a Retry-After HTTP header indicating the minimum number of seconds that clients SHOULD wait before attempting the operation again. - -##### ErrorResponse : Object - -Property | Type | Required | Description --------- | ---- | -------- | ----------- -`error` | Error | ✔ | The error object. - -##### Error : Object - -Property | Type | Required | Description --------- | ---- | -------- | ----------- -`code` | String | ✔ | One of a server-defined set of error codes. -`message` | String | ✔ | A human-readable representation of the error. -`target` | String | | The target of the error. -`details` | Error[] | | An array of details about specific errors that led to this reported error. -`innererror` | InnerError | | An object containing more specific information than the current object about the error. - -##### InnerError : Object - -Property | Type | Required | Description --------- | ---- | -------- | ----------- -`code` | String | | A more specific error code than was provided by the containing error. -`innererror` | InnerError | | An object containing more specific information than the current object about the error. - -##### Examples - -Example of "innererror": - -```json -{ - "error": { - "code": "BadArgument", - "message": "Previous passwords may not be reused", - "target": "password", - "innererror": { - "code": "PasswordError", - "innererror": { - "code": "PasswordDoesNotMeetPolicy", - "minLength": "6", - "maxLength": "64", - "characterTypes": ["lowerCase","upperCase","number","symbol"], - "minDistinctCharacterTypes": "2", - "innererror": { - "code": "PasswordReuseNotAllowed" - } - } - } - } -} -``` - -In this example, the most basic error code is "BadArgument", but for clients that are interested, there are more specific error codes in "innererror." -The "PasswordReuseNotAllowed" code may have been added by the service at a later date, having previously only returned "PasswordDoesNotMeetPolicy." -Existing clients do not break when the new error code is added, but new clients MAY take advantage of it. -The "PasswordDoesNotMeetPolicy" error also includes additional name/value pairs that allow the client to determine the server's configuration, validate the user's input programmatically, or present the server's constraints to the user within the client's own localized messaging. - -Example of "details": - -```json -{ - "error": { - "code": "BadArgument", - "message": "Multiple errors in ContactInfo data", - "target": "ContactInfo", - "details": [ - { - "code": "NullValue", - "target": "PhoneNumber", - "message": "Phone number must not be null" - }, - { - "code": "NullValue", - "target": "LastName", - "message": "Last name must not be null" - }, - { - "code": "MalformedValue", - "target": "Address", - "message": "Address is not valid" - } - ] - } -} -``` - -In this example there were multiple problems with the request, with each individual error listed in "details." - -### 7.11. HTTP Status Codes -Standard HTTP Status Codes SHOULD be used; see the HTTP Status Code definitions for more information. - -### 7.12. Client library optional -Developers MUST be able to develop on a wide variety of platforms and languages, such as Windows, macOS, Linux, C#, Python, Node.js, and Ruby. - -Services SHOULD be able to be accessed from simple HTTP tools such as curl without significant effort. - -Service developer portals SHOULD provide the equivalent of "Get Developer Token" to facilitate experimentation and curl support. - -## 8. CORS -Services compliant with the Microsoft REST API Guidelines MUST support [CORS (Cross Origin Resource Sharing)][cors]. -Services SHOULD support an allowed origin of CORS * and enforce authorization through valid OAuth tokens. -Services SHOULD NOT support user credentials with origin validation. -There MAY be exceptions for special cases. - -### 8.1. Client guidance -Web developers usually don't need to do anything special to take advantage of CORS. -All of the handshake steps happen invisibly as part of the standard XMLHttpRequest calls they make. - -Many other platforms, such as .NET, have integrated support for CORS. - -#### 8.1.1. Avoiding preflight -Because the CORS protocol can trigger preflight requests that add additional round trips to the server, performance-critical apps might be interested in avoiding them. -The spirit behind CORS is to avoid preflight for any simple cross-domain requests that old non-CORS-capable browsers were able to make. -All other requests require preflight. - -A request is "simple" and avoids preflight if its method is GET, HEAD or POST, and if it doesn't contain any request headers besides Accept, Accept-Language and Content-Language. -For POST requests, the Content-Type header is also allowed, but only if its value is "application/x-www-form-urlencoded", "multipart/form-data" or "text/plain." -For any other headers or values, a preflight request will happen. - -### 8.2. Service guidance - At minimum, services MUST: -- Understand the Origin request header that browsers send on cross-domain requests, and the Access-Control-Request-Method request header that they send on preflight OPTIONS requests that check for access. -- If the Origin header is present in a request: - - If the request uses the OPTIONS method and contains the Access-Control-Request-Method header, then it is a preflight request intended to probe for access before the actual request. Otherwise, it is an actual request. For preflight requests, beyond performing the steps below to add headers, services MUST perform no additional processing and MUST return a 200 OK. For non-preflight requests, the headers below are added in addition to the request's regular processing. - - Add an Access-Control-Allow-Origin header to the response, containing the same value as the Origin request header. Note that this requires services to dynamically generate the header value. Resources that do not require cookies or any other form of [user credentials][cors-user-credentials] MAY respond with a wildcard asterisk (*) instead. Note that the wildcard is acceptable here only, and not for any of the other headers described below. - - If the caller requires access to a response header that is not in the set of [simple response headers][cors-simple-headers] (Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma), then add an Access-Control-Expose-Headers header containing the list of additional response header names the client should have access to. - - If the request requires cookies, then add an Access-Control-Allow-Credentials header set to "true." - - If the request was a preflight request (see first bullet), then the service MUST: - - Add an Access-Control-Allow-Headers response header containing the list of request header names the client is permitted to use. This list need only contain headers that are not in the set of [simple request headers][cors-simple-headers] (Accept, Accept-Language, Content-Language). If there are no restrictions on headers the service accepts, the service MAY simply return the same value as the Access-Control-Request-Headers header sent by the client. - - Add an Access-Control-Allow-Methods response header containing the list of HTTP methods the caller is permitted to use. - -Add an Access-Control-Max-Age pref response header containing the number of seconds for which this preflight response is valid (and hence can be avoided before subsequent actual requests). Note that while it is customary to use a large value like 2592000 (30 days), many browsers self-impose a much lower limit (e.g., five minutes). - -Because browser preflight response caches are notoriously weak, the additional round trip from a preflight response hurts performance. -Services used by interactive Web clients where performance is critical SHOULD avoid patterns that cause a preflight request -- For GET and HEAD calls, avoid requiring request headers that are not part of the simple set above. Allow them to be provided as query parameters instead. - - The Authorization header is not part of the simple set, so the authentication token MUST be sent through the "access_token" query parameter instead, for resources requiring authentication. Note that passing authentication tokens in the URL is not recommended, because it can lead to the token getting recorded in server logs and exposed to anyone with access to those logs. Services that accept authentication tokens through the URL MUST take steps to mitigate the security risks, such as using short-lived authentication tokens, suppressing the auth token from getting logged, and controlling access to server logs. - -- Avoid requiring cookies. XmlHttpRequest will only send cookies on cross-domain requests if the "withCredentials" attribute is set; this also causes a preflight request. - - Services that require cookie-based authentication MUST use a "dynamic canary" to secure all APIs that accept cookies. - -- For POST calls, prefer simple Content-Types in the set of ("application/x-www-form-urlencoded", "multipart/form-data", "text/plain") where applicable. Any other Content-Type will induce a preflight request. - - Services MUST NOT contravene other API recommendations in the name of avoiding CORS preflight requests. In particular, in accordance with recommendations, most POST requests will actually require a preflight request due to the Content-Type. - - If eliminating preflight is critical, then a service MAY support alternative mechanisms for data transfer, but the RECOMMENDED approach MUST also be supported. - -In addition, when appropriate services MAY support the JSONP pattern for simple, GET-only cross-domain access. -In JSONP, services take a parameter indicating the format (_$format=json_) and a parameter indicating a callback (_$callback=someFunc_), and return a text/javascript document containing the JSON response wrapped in a function call with the indicated name. -More on JSONP at Wikipedia: [JSONP](https://en.wikipedia.org/wiki/JSONP). - -## 9. Collections -### 9.1. Item keys -Services MAY support durable identifiers for each item in the collection, and that identifier SHOULD be represented in JSON as "id". These durable identifiers are often used as item keys. - -Collections that support durable identifiers MAY support delta queries. - -### 9.2. Serialization -Collections are represented in JSON using standard array notation. - -### 9.3. Collection URL patterns -Collections are located directly under the service root when they are top level, or as a segment under another resource when scoped to that resource. - -For example: - -```http -GET https://api.contoso.com/v1.0/people -``` - -Whenever possible, services MUST support the "/" pattern. -For example: - -```http -GET https://{serviceRoot}/{collection}/{id} -``` - -Where: -- {serviceRoot} – the combination of host (site URL) + the root path to the service -- {collection} – the name of the collection, unabbreviated, pluralized -- {id} – the value of the unique id property. When using the "/" pattern this MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. - -#### 9.3.1. Nested collections and properties -Collection items MAY contain other collections. -For example, a user collection MAY contain user resources that have multiple addresses: - -```http -GET https://api.contoso.com/v1.0/people/123/addresses -``` - -```json -{ - "value": [ - { "street": "1st Avenue", "city": "Seattle" }, - { "street": "124th Ave NE", "city": "Redmond" } - ] -} -``` - -### 9.4. Big collections -As data grows, so do collections. -Planning for pagination is important for all services. -Therefore, when multiple pages are available, the serialization payload MUST contain the opaque URL for the next page as appropriate. -Refer to the paging guidance for more details. - -Clients MUST be resilient to collection data being either paged or nonpaged for any given request. - -```json -{ - "value":[ - { "id": "Item 1","price": 99.95,"sizes": null}, - { … }, - { … }, - { "id": "Item 99","price": 59.99,"sizes": null} - ], - "@nextLink": "{opaqueUrl}" -} -``` - -### 9.5. Changing collections -POST requests are not idempotent. -This means that two POST requests sent to a collection resource with exactly the same payload MAY lead to multiple items being created in that collection. -This is often the case for insert operations on items with a server-side generated id. - -For example, the following request: - -```http -POST https://api.contoso.com/v1.0/people -``` - -Would lead to a response indicating the location of the new collection item: - -```http -201 Created -Location: https://api.contoso.com/v1.0/people/123 -``` - -And once executed again, would likely lead to another resource: - -```http -201 Created -Location: https://api.contoso.com/v1.0/people/124 -``` - -While a PUT request would require the indication of the collection item with the corresponding key instead: - -```http -PUT https://api.contoso.com/v1.0/people/123 -``` - -### 9.6. Sorting collections -The results of a collection query MAY be sorted based on property values. -The property is determined by the value of the _$orderBy_ query parameter. - -The value of the _$orderBy_ parameter contains a comma-separated list of expressions used to sort the items. -A special case of such an expression is a property path terminating on a primitive property. - -The expression MAY include the suffix "asc" for ascending or "desc" for descending, separated from the property name by one or more spaces. -If "asc" or "desc" is not specified, the service MUST order by the specified property in ascending order. - -NULL values MUST sort as "less than" non-NULL values. - -Items MUST be sorted by the result values of the first expression, and then items with the same value for the first expression are sorted by the result value of the second expression, and so on. -The sort order is the inherent order for the type of the property. - -For example: - -```http -GET https://api.contoso.com/v1.0/people?$orderBy=name -``` - -Will return all people sorted by name in ascending order. - -For example: - -```http -GET https://api.contoso.com/v1.0/people?$orderBy=name desc -``` - -Will return all people sorted by name in descending order. - -Sub-sorts can be specified by a comma-separated list of property names with OPTIONAL direction qualifier. - -For example: - -```http -GET https://api.contoso.com/v1.0/people?$orderBy=name desc,hireDate -``` - -Will return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. - -Sorting MUST compose with filtering such that: - -```http -GET https://api.contoso.com/v1.0/people?$filter=name eq 'david'&$orderBy=hireDate -``` - -Will return all people whose name is David sorted in ascending order by hireDate. - -#### 9.6.1. Interpreting a sorting expression -Sorting parameters MUST be consistent across pages, as both client and server-side paging is fully compatible with sorting. - -If a service does not support sorting by a property named in a _$orderBy_ expression, the service MUST respond with an error message as defined in the Responding to Unsupported Requests section. - -### 9.7. Filtering -The _$filter_ querystring parameter allows clients to filter a collection of resources that are addressed by a request URL. -The expression specified with _$filter_ is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. -Resources for which the expression evaluates to false or to null, or which reference properties that are unavailable due to permissions, are omitted from the response. - -Example: return all Products whose Price is less than $10.00 - -```http -GET https://api.contoso.com/v1.0/products?$filter=price lt 10.00 -``` - -The value of the _$filter_ option is a Boolean expression. - -#### 9.7.1. Filter operations -Services that support _$filter_ SHOULD support the following minimal set of operations. - -Operator | Description | Example --------------------- | --------------------- | ----------------------------------------------------- -Comparison Operators | | -eq | Equal | city eq 'Redmond' -ne | Not equal | city ne 'London' -gt | Greater than | price gt 20 -ge | Greater than or equal | price ge 10 -lt | Less than | price lt 20 -le | Less than or equal | price le 100 -Logical Operators | | -and | Logical and | price le 200 and price gt 3.5 -or | Logical or | price le 3.5 or price gt 200 -not | Logical negation | not price le 3.5 -Grouping Operators | | -( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 - -#### 9.7.2. Operator examples -The following examples illustrate the use and semantics of each of the logical operators. - -Example: all products with a name equal to 'Milk' - -```http -GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' -``` - -Example: all products with a name not equal to 'Milk' - -```http -GET https://api.contoso.com/v1.0/products?$filter=name ne 'Milk' -``` - -Example: all products with the name 'Milk' that also have a price less than 2.55: - -```http -GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' and price lt 2.55 -``` - -Example: all products that either have the name 'Milk' or have a price less than 2.55: - -```http -GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' or price lt 2.55 -``` - -Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: - -```http -GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 -``` - -#### 9.7.3. Operator precedence -Services MUST use the following operator precedence for supported operators when evaluating _$filter_ expressions. -Operators are listed by category in order of precedence from highest to lowest. -Operators in the same category have equal precedence: - -| Group | Operator | Description | -|:----------------|:---------|:----------------------| -| Grouping | ( ) | Precedence grouping | -| Unary | not | Logical Negation | -| Relational | gt | Greater Than | -| | ge | Greater than or Equal | -| | lt | Less Than | -| | le | Less than or Equal | -| Equality | eq | Equal | -| | ne | Not Equal | -| Conditional AND | and | Logical And | -| Conditional OR | or | Logical Or | - -### 9.8. Pagination -RESTful APIs that return collections MAY return partial sets. -Consumers of these services MUST expect partial result sets and correctly page through to retrieve an entire set. - -There are two forms of pagination that MAY be supported by RESTful APIs. -Server-driven paging mitigates against denial-of-service attacks by forcibly paginating a request over multiple response payloads. -Client-driven paging enables clients to request only the number of resources that it can use at a given time. - -Sorting and Filtering parameters MUST be consistent across pages, because both client- and server-side paging is fully compatible with both filtering and sorting. - -#### 9.8.1. Continuation tokens - -Paginated responses MUST indicate a partial result by including a continuation token in the response using the OData control information `@nextLink`. -The absence of a continuation token means that no additional pages are available. - -Clients MUST treat the continuation URL as opaque, which means that query options may not be changed while iterating over a set of partial results. - -Example: - -```http -GET http://api.contoso.com/v1.0/people HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - ..., - "value": [...], - "@nextLink": "{opaqueUrl}" -} -``` - -The `@nextLink` MAY be populated using either server-driven paging or client-driven paging (`@nextLink`s generated using client-driven paging should not include the `$top` query parameter). - -#### 9.8.2. Server-driven paging - -The server MAY provide server-driven paging by populating the continuation token with a `$skiptoken` query parameter. -The `$skiptoken` value is opaque for clients and its structure should not be assumed. -`$skiptoken` values SHOULD expire after some period of time decided by the server. - -Example: - -```http -GET http://api.contoso.com/v1.0/people HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - ..., - "value": [...], - "@nextLink": "http://api.contoso.com/v1.0/people?$skiptoken={opaquetoken}" -} -``` - -```http -GET http://api.contoso.com/v1.0/people?$skiptoken={opaquetoken} HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - ..., - "value": [...], - "@nextLink": "http://api.contoso.com/v1.0/people?$skiptoken={opaquetoken2}" -} -``` - -#### 9.8.3. Client-driven paging -Clients MAY use _$top_ and _$skip_ query parameters to specify a number of results to return and an offset into the collection. - -The server SHOULD honor the values specified by the client; however, clients MUST be prepared to handle responses that contain a different page size or contain a continuation token. - -When both _$top_ and _$skip_ are given by a client, the server SHOULD first apply _$skip_ and then _$top_ on the collection. - -Note: If the server can't honor _$top_ and/or _$skip_, the server MUST return an error to the client informing about it instead of just ignoring the query options. -This will avoid the risk of the client making assumptions about the data returned. - -Example: - -```http -GET http://api.contoso.com/v1.0/people?$top=5&$skip=2 HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - ..., - "value": [...] -} -``` - -#### 9.8.4. Additional considerations -**Stable order prerequisite:** Both forms of paging depend on the collection of items having a stable order. -The server MUST supplement any specified order criteria with additional sorts (typically by key) to ensure that items are always ordered consistently. - -**Missing/repeated results:** Even if the server enforces a consistent sort order, results MAY be missing or repeated based on creation or deletion of other resources. -Clients MUST be prepared to deal with these discrepancies. -The server SHOULD always encode the record ID of the last read record, helping the client in the process of managing repeated/missing results. - -**Combining client- and server-driven paging:** Note that client-driven paging does not preclude server-driven paging. -If the page size requested by the client is larger than the default page size supported by the server, the expected response would be the number of results specified by the client, paginated as specified by the server paging settings. - -**Page Size:** Clients MAY request server-driven paging with a specific page size by specifying a _$maxpagesize_ preference. -The server SHOULD honor this preference if the specified page size is smaller than the server's default page size. - -**Paginating embedded collections:** It is possible for both client-driven paging and server-driven paging to be applied to embedded collections. -If a server paginates an embedded collection, it MUST include additional continuation tokens as appropriate. - -**Recordset count:** Developers who want to know the full number of records across all pages, MAY include the query parameter _$count=true_ to tell the server to include the count of items in the response. - -### 9.9. Compound collection operations -Filtering, Sorting and Pagination operations MAY all be performed against a given collection. -When these operations are performed together, the evaluation order MUST be: - -1. **Filtering**. This includes all range expressions performed as an AND operation. -2. **Sorting**. The potentially filtered list is sorted according to the sort criteria. -3. **Pagination**. The materialized paginated view is presented over the filtered, sorted list. This applies to both server-driven pagination and client-driven pagination. - -### 9.10. Empty Results -When a filter is performed on a collection and the result set is empty you MUST respond with a valid response body and a 200 response code. -In this example the filters supplied by the client resulted in a empty result set. -The response body is returned as normal and the _value_ attribute is set to a empty collection. -A client MAY be expecting metadata attributes like _maxItems_ based on the format of your responses to similar calls which produced results. -You SHOULD maintain consistency in your API whenever possible. - -```http -GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - ..., - "maxItems": 0, - "value": [] -} -``` - -## 10. Delta queries -Services MAY choose to support delta queries. - -### 10.1. Delta links -Delta links are opaque, service-generated links that the client uses to retrieve subsequent changes to a result. - -At a conceptual level delta links are based on a defining query that describes the set of results for which changes are being tracked. -The delta link encodes the collection of entities for which changes are being tracked, along with a starting point from which to track changes. - -If the query contains a filter, the response MUST include only changes to entities matching the specified criteria. -The key principles of the Delta Query are: -- Every item in the set MUST have a persistent identifier. That identifier SHOULD be represented as "id". This identifier is a service defined opaque string that MAY be used by the client to track object across calls. -- The delta MUST contain an entry for each entity that newly matches the specified criteria, and MUST contain a "@removed" entry for each entity that no longer matches the criteria. -- Re-evaluate the query and compare it to original set of results; every entry uniquely in the current set MUST be returned as an Add operation, and every entry uniquely in the original set MUST be returned as a "remove" operation. -- Each entity that previously did not match the criteria but matches it now MUST be returned as an "add"; conversely, each entity that previously matched the query but no longer does MUST be returned as a "@removed" entry. -- Entities that have changed MUST be included in the set using their standard representation. -- Services MAY add additional metadata to the "@removed" node, such as a reason for removal, or a "removed at" timestamp. We recommend teams coordinate with the Microsoft REST API Guidelines Working Group on extensions to help maintain consistency. - -The delta link MUST NOT encode any client top or skip value. - -### 10.2. Entity representation -Added and updated entities are represented in the entity set using their standard representation. -From the perspective of the set, there is no difference between an added or updated entity. - -Removed entities are represented using only their "id" and an "@removed" node. -The presence of an "@removed" node MUST represent the removal of the entry from the set. - -### 10.3. Obtaining a delta link -A delta link is obtained by querying a collection or entity and appending a $delta query string parameter. -For example: - -```http -GET https://api.contoso.com/v1.0/people?$delta -HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "value":[ - { "id": "1", "name": "Matt"}, - { "id": "2", "name": "Mark"}, - { "id": "3", "name": "John"} - ], - "@deltaLink": "{opaqueUrl}" -} -``` - -Note: If the collection is paginated the deltaLink will only be present on the final page but MUST reflect any changes to the data returned across all pages. - -### 10.4. Contents of a delta link response -Added/Updated entries MUST appear as regular JSON objects, with regular item properties. -Returning the added/modified items in their regular representation allows the client to merge them into their existing "cache" using standard merge concepts based on the "id" field. - -Entries removed from the defined collection MUST be included in the response. -Items removed from the set MUST be represented using only their "id" and an "@removed" node. - -### 10.5. Using a delta link -The client requests changes by invoking the GET method on the delta link. -The client MUST use the delta URL as is -- in other words the client MUST NOT modify the URL in any way (e.g., parsing it and adding additional query string parameters). -In this example: - -```http -GET https://{opaqueUrl} HTTP/1.1 -Accept: application/json - -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "value":[ - { "id": "1", "name": "Mat"}, - { "id": "2", "name": "Marc"}, - { "id": "3", "@removed": {} }, - { "id": "4", "name": "Luc"} - ], - "@deltaLink": "{opaqueUrl}" -} -``` - -The results of a request against the delta link may span multiple pages but MUST be ordered by the service across all pages in such a way as to ensure a deterministic result when applied in order to the response that contained the delta link. - -If no changes have occurred, the response is an empty collection that contains a delta link for subsequent changes if requested. -This delta link MAY be identical to the delta link resulting in the empty collection of changes. - -If the delta link is no longer valid, the service MUST respond with _410 Gone_. The response SHOULD include a Location header that the client can use to retrieve a new baseline set of results. - -## 11. JSON standardizations -### 11.1. JSON formatting standardization for primitive types -Primitive values MUST be serialized to JSON following the rules of [RFC8259][rfc-8259]. - -**Important note for 64bit integers:** JavaScript will silently truncate integers larger than `Number.MAX_SAFE_INTEGER` (2^53-1) or numbers smaller than `Number.MIN_SAFE_INTEGER` (-2^53+1). If the service is expected to return integer values outside the range of safe values, strongly consider returning the value as a string in order to maximize interoperability and avoid data loss. - -### 11.2. Guidelines for dates and times -#### 11.2.1. Producing dates -Services MUST produce dates using the `DateLiteral` format, and SHOULD use the `Iso8601Literal` format unless there are compelling reasons to do otherwise. -Services that do use the `StructuredDateLiteral` format MUST NOT produce dates using the `T` kind unless BOTH the additional precision is REQUIRED, and ECMAScript clients are explicitly unsupported. -(Non-Normative statement: When deciding which particular `DateKind` to standardize on, the approximate order of preference is `E, C, U, W, O, X, I, T`. -This optimizes for ECMAScript, .NET, and C++ programmers, in that order.) - -#### 11.2.2. Consuming dates -Services MUST accept dates from clients that use the same `DateLiteral` format (including the `DateKind`, if applicable) that they produce, and SHOULD accept dates using any `DateLiteral` format. - -#### 11.2.3. Compatibility -Services MUST use the same `DateLiteral` format (including the same `DateKind`, if applicable) for all resources of the same type, and SHOULD use the same `DateLiteral` format (and `DateKind`, if applicable) for all resources across the entire service. - -Any change to the `DateLiteral` format produced by the service (including the `DateKind`, if applicable) and any reductions in the `DateLiteral` formats (and `DateKind`, if applicable) accepted by the service MUST be treated as a breaking change. -Any widening of the `DateLiteral` formats accepted by the service is NOT considered a breaking change. - -### 11.3. JSON serialization of dates and times -Round-tripping serialized dates with JSON is a hard problem. -Although ECMAScript supports literals for most built-in types, it does not define a literal format for dates. -The Web has coalesced around the [ECMAScript subset of ISO 8601 date formats (ISO 8601)][iso-8601], but there are situations where this format is not desirable. -For those cases, this document defines a JSON serialization format that can be used to unambiguously represent dates in different formats. -Other serialization formats (such as XML) could be derived from this format. - -#### 11.3.1. The `DateLiteral` format -Dates represented in JSON are serialized using the following grammar. -Informally, a `DateValue` is either an ISO 8601-formatted string or a JSON object containing two properties named `kind` and `value` that together define a point in time. -The following is not a context-free grammar; in particular, the interpretation of `DateValue` depends on the value of `DateKind`, but this minimizes the number of productions required to describe the format. - -``` -DateLiteral: - Iso8601Literal - StructuredDateLiteral - -Iso8601Literal: - A string literal as defined in https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15. Note that the full grammar for ISO 8601 (such as "basic format" without separators) is not supported. - All dates default to UTC unless specified otherwise. - -StructuredDateLiteral: - { DateKindProperty , DateValueProperty } - { DateValueProperty , DateKindProperty } - -DateKindProperty - "kind" : DateKind - -DateKind: - "C" ; see below - "E" ; see below - "I" ; see below - "O" ; see below - "T" ; see below - "U" ; see below - "W" ; see below - "X" ; see below - -DateValueProperty: - "value" : DateValue - -DateValue: - UnsignedInteger ; not defined here - SignedInteger ; not defined here - RealNumber ; not defined here - Iso8601Literal ; as above -``` - -#### 11.3.2. Commentary on date formatting -A `DateLiteral` using the `Iso8601Literal` production is relatively straightforward. -Here is an example of an object with a property named `creationDate` that is set to February 13, 2015, at 1:15 p.m. UTC: - -```json -{ "creationDate" : "2015-02-13T13:15Z" } -``` - -The `StructuredDateLiteral` consists of a `DateKind` and an accompanying `DateValue` whose valid values (and their interpretation) depend on the `DateKind`. The following table describes the valid combinations and their meaning: - -DateKind | DateValue | Colloquial Name & Interpretation | More Info --------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- -C | UnsignedInteger | "CLR"; number of milliseconds since midnight January 1, 0001; negative values are not allowed. *See note below.* | [MSDN][clr-time] -E | SignedInteger | "ECMAScript"; number of milliseconds since midnight, January 1, 1970. | [ECMA International][ecmascript-time] -I | Iso8601Literal | "ISO 8601"; a string limited to the ECMAScript subset. | -O | RealNumber | "OLE Date"; integral part is the number of days since midnight, December 31, 1899, and fractional part is the time within the day (0.5 = midday). | [MSDN][ole-date] -T | SignedInteger | "Ticks"; number of ticks (100-nanosecond intervals) since midnight January 1, 1601. *See note below.* | [MSDN][ticks-time] -U | SignedInteger | "UNIX"; number of seconds since midnight, January 1, 1970. | [MSDN][unix-time] -W | SignedInteger | "Windows"; number of milliseconds since midnight January 1, 1601. *See note below.* | [MSDN][windows-time] -X | RealNumber | "Excel"; as for `O` but the year 1900 is incorrectly treated as a leap year, and day 0 is "January 0 (zero)". | [Microsoft Support][excel-time] - -**Important note for `C` and `W` kinds:** The native CLR and Windows times are represented by 100-nanosecond "tick" values. -To interoperate with ECMAScript clients that have limited precision, _these values MUST be converted to and from milliseconds_ when (de)serialized as a `DateLiteral`. -One millisecond is equivalent to 10,000 ticks. - -**Important note for `T` kind:** This kind preserves the full fidelity of the Windows native time formats (and is trivially convertible to and from the native CLR format) but is incompatible with ECMAScript clients. -Therefore, its use SHOULD be limited to only those scenarios that both require the additional precision and do not need to interoperate with ECMAScript clients. - -Here is the same example of an object with a property named creationDate that is set to February 13, 2015, at 1:15 p.m. UTC, using several formats: - -```json -[ - { "creationDate" : { "kind" : "O", "value" : 42048.55 } }, - { "creationDate" : { "kind" : "E", "value" : 1423862100000 } } -] -``` - -One of the benefits of separating the kind from the value is that once a client knows the kind used by a particular service, it can interpret the value without requiring any additional parsing. -In the common case of the value being a number, this makes coding easier for developers: - -```csharp -// We know this service always gives out ECMAScript-format dates -var date = new Date(serverResponse.someObject.creationDate.value); -``` - -### 11.4. Durations -[Durations][wikipedia-iso8601-durations] need to be serialized in conformance with [ISO 8601][wikipedia-iso8601-durations]. -Durations are "represented by the format `P[n]Y[n]M[n]DT[n]H[n]M[n]S`." -From the standard: -- P is the duration designator (historically called "period") placed at the start of the duration representation. -- Y is the year designator that follows the value for the number of years. -- M is the month designator that follows the value for the number of months. -- W is the week designator that follows the value for the number of weeks. -- D is the day designator that follows the value for the number of days. -- T is the time designator that precedes the time components of the representation. -- H is the hour designator that follows the value for the number of hours. -- M is the minute designator that follows the value for the number of minutes. -- S is the second designator that follows the value for the number of seconds. - -For example, "P3Y6M4DT12H30M5S" represents a duration of "three years, six months, four days, twelve hours, thirty minutes, and five seconds." - -### 11.5. Intervals -[Intervals][wikipedia-iso8601-intervals] are defined as part of [ISO 8601][wikipedia-iso8601-intervals]. -- Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z" -- Start and duration, such as "2007-03-01T13:00:00Z/P1Y2M10DT2H30M" -- Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z" -- Duration only, such as "P1Y2M10DT2H30M", with additional context information - -### 11.6. Repeating intervals -[Repeating Intervals][wikipedia-iso8601-repeatingintervals], as per [ISO 8601][wikipedia-iso8601-repeatingintervals], are: - -> Formed by adding "R[n]/" to the beginning of an interval expression, where R is used as the letter itself and [n] is replaced by the number of repetitions. -Leaving out the value for [n] means an unbounded number of repetitions. - -For example, to repeat the interval of "P1Y2M10DT2H30M" five times starting at "2008-03-01T13:00:00Z", use "R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M." - -## 12. Versioning -**All APIs compliant with the Microsoft REST API Guidelines MUST support explicit versioning.** It's critical that clients can count on services to be stable over time, and it's critical that services can add features and make changes. - -### 12.1. Versioning formats -Services are versioned using a Major.Minor versioning scheme. -Services MAY opt for a "Major" only version scheme in which case the ".0" is implied and all other rules in this section apply. -Two options for specifying the version of a REST API request are supported: -- Embedded in the path of the request URL, at the end of the service root: `https://api.contoso.com/v1.0/products/users` -- As a query string parameter of the URL: `https://api.contoso.com/products/users?api-version=1.0` - -Guidance for choosing between the two options is as follows: - -1. Services co-located behind a DNS endpoint MUST use the same versioning mechanism. -2. In this scenario, a consistent user experience across the endpoint is paramount. The Microsoft REST API Guidelines Working Group recommends that new top-level DNS endpoints are not created without explicit conversations with your organization's leadership team. -3. Services that guarantee the stability of their REST API's URL paths, even through future versions of the API, MAY adopt the query string parameter mechanism. This means the naming and structure of the relationships described in the API cannot evolve after the API ships, even across versions with breaking changes. -4. Services that cannot ensure URL path stability across future versions MUST embed the version in the URL path. - -Certain bedrock services such as Microsoft's Azure Active Directory may be exposed behind multiple endpoints. -Such services MUST support the versioning mechanisms of each endpoint, even if that means supporting multiple versioning mechanisms. - -#### 12.1.1. Group versioning -Group versioning is an OPTIONAL feature that MAY be offered on services using the query string parameter mechanism. -Group versions allow for logical grouping of API endpoints under a common versioning moniker. -This allows developers to look up a single version number and use it across multiple endpoints. -Group version numbers are well known, and services SHOULD reject any unrecognized values. - -Internally, services will take a Group Version and map it to the appropriate Major.Minor version. - -The Group Version format is defined as YYYY-MM-DD, for example 2012-12-07 for December 7, 2012. This Date versioning format applies only to Group Versions and SHOULD NOT be used as an alternative to Major.Minor versioning. - -##### Examples of group versioning - -| Group | Major.Minor | -|:-----------|:------------| -| 2012-12-01 | 1.0 | -| | 1.1 | -| | 1.2 | -| 2013-03-21 | 1.0 | -| | 2.0 | -| | 3.0 | -| | 3.1 | -| | 3.2 | -| | 3.3 | - -Version Format | Example | Interpretation ------------------------------ | ---------------------- | ------------------------------------------ -{groupVersion} | 2013-03-21, 2012-12-01 | 3.3, 1.2 -{majorVersion} | 3 | 3.0 -{majorVersion}.{minorVersion} | 1.2 | 1.2 - -Clients can specify either the group version or the Major.Minor version: - -For example: - -```http -GET http://api.contoso.com/acct1/c1/blob2?api-version=1.0 -``` - -```http -PUT http://api.contoso.com/acct1/c1/b2?api-version=2011-12-07 -``` - -### 12.2. When to version -Services MUST increment their version number in response to any breaking API change. -See the following section for a detailed discussion of what constitutes a breaking change. -Services MAY increment their version number for nonbreaking changes as well, if desired. - -Use a new major version number to signal that support for existing clients will be deprecated in the future. -When introducing a new major version, services MUST provide a clear upgrade path for existing clients and develop a plan for deprecation that is consistent with their business group's policies. -Services SHOULD use a new minor version number for all other changes. - -Online documentation of versioned services MUST indicate the current support status of each previous API version and provide a path to the latest version. - -### 12.3. Definition of a breaking change -Changes to the contract of an API are considered a breaking change. -Changes that impact the backwards compatibility of an API are a breaking change. - -Teams MAY define backwards compatibility as their business needs require. -For example, Azure defines the addition of a new JSON field in a response to be not backwards compatible. -Office 365 has a looser definition of backwards compatibility and allows JSON fields to be added to responses. - -Clear examples of breaking changes: - -1. Removing or renaming APIs or API parameters -2. Changes in behavior for an existing API -3. Changes in Error Codes and Fault Contracts -4. Anything that would violate the [Principle of Least Astonishment][principle-of-least-astonishment] - -Services MUST explicitly define their definition of a breaking change, especially with regard to adding new fields to JSON responses and adding new API arguments with default fields. -Services that are co-located behind a DNS Endpoint with other services MUST be consistent in defining contract extensibility. - -The applicable changes described [in this section of the OData V4 spec][odata-breaking-changes] SHOULD be considered part of the minimum bar that all services MUST consider a breaking change. - -## 13. Long running operations -Long running operations, sometimes called async operations, tend to mean different things to different people. -This section sets forth guidance around different types of long running operations, and describes the wire protocols and best practices for these types of operations. - -1. One or more clients MUST be able to monitor and operate on the same resource at the same time. -2. The state of the system SHOULD be discoverable and testable at all times. Clients SHOULD be able to determine the system state even if the operation tracking resource is no longer active. The act of querying the state of a long running operation should itself leverage principles of the web. i.e. well-defined resources with uniform interface semantics. Clients MAY issue a GET on some resource to determine the state of a long running operation -3. Long running operations SHOULD work for clients looking to "Fire and Forget" and for clients looking to actively monitor and act upon results. -4. Cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a cancelled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. - -### 13.1. Resource based long running operations (RELO) -Resource based modeling is where the status of an operation is encoded in the resource and the wire protocol used is the standard synchronous protocol. -In this model state transitions are well defined and goal states are similarly defined. - -_This is the preferred model for long running operations and should be used wherever possible._ Avoiding the complexity and mechanics of the LRO Wire Protocol makes things simpler for our users and tooling chain. - -An example may be a machine reboot, where the operation itself completes synchronously but the GET operation on the virtual machine resource would have a "state: Rebooting", "state: Running" that could be queried at any time. - -This model MAY integrate Push Notifications. - -While most operations are likely to be POST semantics, in addition to POST semantics, services MAY support PUT semantics via routing to simplify their APIs. -For example, a user that wants to create a database named "db1" could call: - -```http -PUT https://api.contoso.com/v1.0/databases/db1 -``` - -In this scenario the databases segment is processing the PUT operation. - -Services MAY also use the hybrid defined below. - -### 13.2. Stepwise long running operations -A stepwise operation is one that takes a long, and often unpredictable, length of time to complete, and doesn't offer state transition modeled in the resource. -This section outlines the approach that services should use to expose such long running operations. - -Service MAY expose stepwise operations. - -> Stepwise Long Running Operations are sometimes called "Async" operations. -This causes confusion, as it mixes elements of platforms ("Async / await", "promises", "futures") with elements of API operation. -This document uses the term "Stepwise Long Running Operation" or often just "Stepwise Operation" to avoid confusion over the word "Async". - -Services MUST perform as much synchronous validation as practical on stepwise requests. -Services MUST prioritize returning errors in a synchronous way, with the goal of having only "Valid" operations processed using the long running operation wire protocol. - -For an API that's defined as a Stepwise Long Running Operation the service MUST go through the Stepwise Long Running Operation flow even if the operation can be completed immediately. -In other words, APIs must adopt and stick with an LRO pattern and not change patterns based on circumstance. - -#### 13.2.1. PUT -Services MAY enable PUT requests for entity creation. - -```http -PUT https://api.contoso.com/v1.0/databases/db1 -``` - -In this scenario the _databases_ segment is processing the PUT operation. - -```http -HTTP/1.1 202 Accepted -Operation-Location: https://api.contoso.com/v1.0/operations/123 -``` - -For services that need to return a 201 Created here, use the hybrid flow described below. - -The 202 Accepted should return no body. -The 201 Created case should return the body of the target resource. - -#### 13.2.2. POST -Services MAY enable POST requests for entity creation. - -```http -POST https://api.contoso.com/v1.0/databases/ - -{ - "fileName": "someFile.db", - "color": "red" -} -``` - -```http -HTTP/1.1 202 Accepted -Operation-Location: https://api.contoso.com/v1.0/operations/123 -``` - -#### 13.2.3. POST, hybrid model -Services MAY respond synchronously to POST requests to collections that create a resource even if the resources aren't fully created when the response is generated. -In order to use this pattern, the response MUST include a representation of the incomplete resource and an indication that it is incomplete. - -For example: - -```http -POST https://api.contoso.com/v1.0/databases/ HTTP/1.1 -Host: api.contoso.com -Content-Type: application/json -Accept: application/json - -{ - "fileName": "someFile.db", - "color": "red" -} -``` - -Service response says the database has been created, but indicates the request is not completed by including the Operation-Location header. -In this case the status property in the response payload also indicates the operation has not fully completed. - -```http -HTTP/1.1 201 Created -Location: https://api.contoso.com/v1.0/databases/db1 -Operation-Location: https://api.contoso.com/v1.0/operations/123 - -{ - "databaseName": "db1", - "color": "red", - "Status": "Provisioning", - [ … other fields for "database" …] -} -``` - -#### 13.2.4. Operations resource -Services MAY provide a "/operations" resource at the tenant level. - -Services that provide the "/operations" resource MUST provide GET semantics. -GET MUST enumerate the set of operations, following standard pagination, sorting, and filtering semantics. -The default sort order for this operation MUST be: - -Primary Sort | Secondary Sort ----------------------- | ----------------------- -Not Started Operations | Operation Creation Time -Running Operations | Operation Creation Time -Completed Operations | Operation Creation Time - -Note that "Completed Operations" is a goal state (see below), and may actually be any of several different states such as "successful", "cancelled", "failed" and so forth. - -#### 13.2.5. Operation resource -An operation is a user addressable resource that tracks a stepwise long running operation. -Operations MUST support GET semantics. -The GET operation against an operation MUST return: - -1. The operation resource, it's state, and any extended state relevant to the particular API. -2. 200 OK as the response code. - -Services MAY support operation cancellation by exposing DELETE on the operation. -If supported DELETE operations MUST be idempotent. - -> Note: From an API design perspective, cancellation does not explicitly mean rollback. -On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. -Following a cancelled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. - -Services that do not support operation cancellation MUST return a 405 Method Not Allowed in the event of a DELETE. - -Operations MUST support the following states: - -1. NotStarted -2. Running -3. Succeeded. Terminal State. -4. Failed. Terminal State. - -Services MAY add additional states, such as "Cancelled" or "Partially Completed". Services that support cancellation MUST sufficiently describe their cancellation such that the state of the system can be accurately determined, and any compensating actions may be run. - -Services that support additional states should consider this list of canonical names and avoid creating new names if possible: Cancelling, Cancelled, Aborting, Aborted, Tombstone, Deleting, Deleted. - -An operation MUST contain, and provide in the GET response, the following information: - -1. The timestamp when the operation was created. -2. A timestamp for when the current state was entered. -3. The operation state (notstarted / running / completed). - -Services MAY add additional, API specific, fields into the operation. -The operation status JSON returned looks like: - -```json -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "lastActionDateTime": "2015-06-19T12-01-03.45Z", - "status": "notstarted | running | succeeded | failed" -} -``` - -##### Percent complete -Sometimes it is impossible for services to know with any accuracy when an operation will complete. -Which makes using the Retry-After header problematic. -In that case, services MAY include, in the operationStatus JSON, a percent complete field. - -```json -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "percentComplete": "50", - "status": "running" -} -``` - -In this example the server has indicated to the client that the long running operation is 50% complete. - -##### Target resource location -For operations that result in, or manipulate, a resource the service MUST include the target resource location in the status upon operation completion. - -```json -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "lastActionDateTime": "2015-06-19T12-06-03.0024Z", - "status": "succeeded", - "resourceLocation": "https://api.contoso.com/v1.0/databases/db1" -} -``` - -#### 13.2.6. Operation tombstones -Services MAY choose to support tombstoned operations. -Services MAY choose to delete tombstones after a service defined period of time. - -#### 13.2.7. The typical flow, polling -- Client invokes a stepwise operation by invoking an action using POST -- The server MUST indicate the request has been started by responding with a 202 Accepted status code. The response SHOULD include the location header containing a URL that the client should poll for the results after waiting the number of seconds specified in the Retry-After header. -- Client polls the location until receiving a 200 response with a terminal operation state. - -##### Example of the typical flow, polling -Client invokes the restart action: - -```http -POST https://api.contoso.com/v1.0/databases HTTP/1.1 -Accept: application/json - -{ - "fromFile": "myFile.db", - "color": "red" -} -``` - -The server response indicates the request has been created. - -```http -HTTP/1.1 202 Accepted -Operation-Location: https://api.contoso.com/v1.0/operations/123 -``` - -Client waits for a period of time then invokes another request to try to get the operation status. - -```http -GET https://api.contoso.com/v1.0/operations/123 -Accept: application/json -``` - -Server responds that results are still not ready and optionally provides a recommendation to wait 30 seconds. - -```http -HTTP/1.1 200 OK -Retry-After: 30 - -{ - "createdDateTime": "2015-06-19T12-01-03.4Z", - "status": "running" -} -``` - -Client waits the recommended 30 seconds and then invokes another request to get the results of the operation. - -```http -GET https://api.contoso.com/v1.0/operations/123 -Accept: application/json -``` - -Server responds with a "status:succeeded" operation that includes the resource location. - -```http -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "createdDateTime": "2015-06-19T12-01-03.45Z", - "lastActionDateTime": "2015-06-19T12-06-03.0024Z", - "status": "succeeded", - "resourceLocation": "https://api.contoso.com/v1.0/databases/db1" -} -``` - -#### 13.2.8. The typical flow, push notifications -1. Client invokes a long running operation by invoking an action using POST. The client has a push notification already setup on the parent resource. -2. The service indicates the request has been started by responding with a 202 Accepted status code. The client ignores everything else. -3. Upon completion of the overall operation the service pushes a notification via the subscription on the parent resource. -4. The client retrieves the operation result via the resource URL. - -##### Example of the typical flow, push notifications existing subscription -Client invokes the backup action. -The client already has a push notification subscription setup for db1. - -```http -POST https://api.contoso.com/v1.0/databases/db1?backup HTTP/1.1 -Accept: application/json -``` - -The server response indicates the request has been accepted. - -```http -HTTP/1.1 202 Accepted -Operation-Location: https://api.contoso.com/v1.0/operations/123 -``` - -The caller ignores all the headers in the return. - -The target URL receives a push notification when the operation is complete. - -```http -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "value": [ - { - "subscriptionId": "1234-5678-1111-2222", - "context": "subscription context that was specified at setup", - "resourceUrl": "https://api.contoso.com/v1.0/databases/db1", - "userId" : "contoso.com/user@contoso.com", - "tenantId" : "contoso.com" - } - ] -} -``` - -#### 13.2.9. Retry-After -In the examples above the Retry-After header indicates the number of seconds that the client should wait before trying to get the result from the URL identified by the location header. - -The HTTP specification allows the Retry-After header to alternatively specify a HTTP date, so clients should be prepared to handle this as well. - -```http -HTTP/1.1 202 Accepted -Operation-Location: http://api.contoso.com/v1.0/operations/123 -Retry-After: 60 -``` - -Note: The use of the HTTP Date is inconsistent with the use of ISO 8601 Date Format used throughout this document, but is explicitly defined by the HTTP standard in [RFC 7231][rfc-7231-7-1-1-1]. Services SHOULD prefer the integer number of seconds (in decimal) format over the HTTP date format. - -### 13.3. Retention policy for operation results -In some situations, the result of a long running operation is not a resource that can be addressed. -For example, if you invoke a long running Action that returns a Boolean (rather than a resource). -In these situations, the Location header points to a place where the Boolean result can be retrieved. - -Which begs the question: "How long should operation results be retained?" - -A recommended minimum retention time is 24 hours. - -Operations SHOULD transition to "tombstone" for an additional period of time prior to being purged from the system. - -## 14. Throttling, Quotas, and Limits -### 14.1. Principles -Services should be as responsive as possible, so as not to block callers. -As a rule of thumb any API call that is expected to take longer than 0.5 seconds in the 99th percentile, should consider using the Long-running Operations pattern for those calls. -Obviously, services cannot guarantee these response times in the face of potentially unlimited load from callers. Services should therefore design and document call request limits for clients, and respond with appropriate, actionable errors and error messages if these limits are exceeded. -Services should respond quickly with an error when they are generally overloaded, rather than simply respond slowly. -Finally, many services will have quotas on calls, perhaps a number of operations per hour or day, usually related to a service plan or price. -When these quotas are exceeded services must also provide immediate, actionable errors. -Quotas and Limits should be scoped to a customer unit: a subscription, a tenant, an application, a plan, or without any other identification a range of ip addresses…as appropriate to the service goals so that the load is properly shared and one unit is not interfering with another. - -### 14.2. Return Codes (429 vs 503) -HTTP specifies two return codes for these scenarios: '429 Too Many Requests' and '503 Service Unavailable'. -Services should use 429 for cases where clients are making too many calls and can fix the situation by changing their call pattern. -Services should respond with 503 in cases where general load or other problems outside the control of the individual callers is responsible for the service becoming slow. -In all cases, services should also provide information suggesting how long the callers should wait before trying in again. -Clients should respect these headers and also implement other transient fault handling techniques. -However, there may be clients that simply retry immediately upon failure, potentially increasing the load on the service. -To handle this, services should design so that returning 429 or 503 is as inexpensive as possible, either by putting in special fastpath code, or ideally by depending on a common frontdoor or load balancer that provides this functionality. - -### 14.3. Retry-After and RateLimit Headers -The Retry-After header is the standard way for responding to clients who are being throttled. -It is also common, but optional, in the case of limits and quotas (but not overall system load) to respond with header describing the limit that was exceeded. -However, services across Microsoft and the industry use a wide range of different headers for this purpose. -We recommend using three headers to describe the limit, the number of calls remaining under the limit, and the time when the limit will reset. -However, other headers may be appropriate for specific types of limits. In all cases these must be documented. - -### 14.4. Service Guidance -Services should choose time windows as appropriate for the SLAs or business objectives. -In the case of Quotas, the Retry-After time and time window may be very long (hours, days, weeks, even months. Services use 429 to indicate the specific caller has made too many calls, and 503 to indicate that the service is load shedding but that it is not the caller’s responsibility. - -#### 14.4.1. Responsiveness -1. Services MUST respond quickly in all circumstances, even when under load. -2. Calls that take longer than 1s to respond in the 99th percentile SHOULD use the Long-Running Operation pattern -3. Calls that take longer than 0.5s to respond in the 99th percentile should strongly consider the LRO pattern -4. Services SHOULD NOT introduce sleeps, pauses, etc. that block callers or are not actionable (“tar-pitting”). - -#### 14.4.2. Rate Limits and Quotas -When a caller has made too many calls - -1. Services MUST return a 429 code -2. Services MUST return a standard error response describing the specifics so that a programmer can make appropriate changes -3. Services MUST return a Retry-After header that indicates how long clients should wait before retrying -4. Services MAY return RateLimit headers that document the limit or quota that has been exceeded -5. Services MAY return RateLimit-Limit: the number of calls the client is allowed to make in a time window -6. Services MAY return RateLimit-Remaining: the number of calls remaining in the time window -7. Services MAY return RateLimit-Reset: the time at which the window resets in UTC epoch seconds -8. Services MAY return other service specific RateLimit headers as appropriate for more detailed information or specific limits or quotas - -#### 14.4.3. Overloaded services -When services are generally overloaded and load shedding - -1. Services MUST Return a 503 code -2. Services MUST Return a standard error response (see 7.10.2) describing the specifics so that a programmer can make appropriate changes -3. Services MUST Return a Retry-After header that indicates how long clients should wait before retrying -4. In the 503 case, the service SHOULD NOT return RateLimit headers - -#### 14.4.4. Example Response - -```http -HTTP/1.1 429 Too Many Requests -Content-Type: application/json -Retry-After: 5 -RateLimit-Limit: 1000 -RateLimit-Remaining: 0 -RateLimit-Reset: 1538152773 -{ - "error": { - "code": "requestLimitExceeded", - "message": "The caller has made too many requests in the time period.", - "details": { - "code": "RateLimit", - "limit": "1000", - "remaining": "0", - "reset": "1538152773", - } - } -} -``` - -### 14.5. Caller Guidance -Callers include all users of the API: tools, portals, other services, not just user clients - -1. Callers MUST wait for a minimum of time indicated in a response with a Retry-After before retrying a request. -2. Callers MAY assume that request is retriable after receiving a response with a Retry-After header without making any changes to the request. -3. Clients SHOULD use shared SDKs and common transient fault libraries to implement the proper behavior - -See: https://docs.microsoft.com/en-us/azure/architecture/best-practices/transient-faults - -### 14.6. Handling callers that ignore Retry-After headers -Ideally, 429 and 503 returns are so low cost that even clients that retry immediately can be handled. -In these cases, if possible the service team should make an effort to contact or fix the client. -If it is a known partner, a bug or incident should be filed. -In extreme cases it may be necessary to use DoS style protections such as blocking the caller. - -## 15. Push notifications via webhooks -### 15.1. Scope -Services MAY implement push notifications via web hooks. -This section addresses the following key scenario: - -> Push notification via HTTP Callbacks, often called Web Hooks, to publicly-addressable servers. - -The approach set forth is chosen due to its simplicity, broad applicability, and low barrier to entry for service subscribers. -It's intended as a minimal set of requirements and as a starting point for additional functionality. - -### 15.2. Principles -The core principles for services that support web hooks are: - -1. Services MUST implement at least a poke/pull model. In the poke/pull model, a notification is sent to a client, and clients then send a request to get the current state or the record of change since their last notification. This approach avoids complexities around message ordering, missed messages, and change sets. Services MAY add more data to provide rich notifications. -2. Services MUST implement the challenge/response protocol for configuring callback URLs. -3. Services SHOULD have a recommended age-out period, with flexibility for services to vary based on scenario. -4. Services SHOULD allow subscriptions that are raising successful notifications to live forever and SHOULD be tolerant of reasonable outage periods. -5. Firehose subscriptions MUST be delivered only over HTTPS. Services SHOULD require other subscription types to be HTTPS. See the "Security" section for more details. - -### 15.3. Types of subscriptions -There are two subscription types, and services MAY implement either, both, or none. -The supported subscription types are: - -1. Firehose subscriptions – a subscription is manually created for the subscribing application, typically in an app registration portal. Notifications of activity that any users have consented to the app receiving are sent to this single subscription. -2. Per-resource subscriptions – the subscribing application uses code to programmatically create a subscription at runtime for some user-specific entity(s). - -Services that support both subscription types SHOULD provide differentiated developer experiences for the two types: - -1. Firehose – Services MUST NOT require developers to create code except to directly verify and respond to notifications. Services MUST provide administrative UI for subscription management. Services SHOULD NOT assume that end users are aware of the subscription, only the subscribing application's functionality. -2. Per-user – Services MUST provide an API for developers to create and manage subscriptions as part of their app as well as verifying and responding to notifications. Services MAY expect end users to be aware of subscriptions and MUST allow end users to revoke subscriptions where they were created directly in response to user actions. - -### 15.4. Call sequences -The call sequence for a firehose subscription MUST follow the diagram below. -It shows manual registration of application and subscription, and then the end user making use of one of the service's APIs. -At this part of the flow, two things MUST be stored: - -1. The service MUST store the end user's act of consent to receiving notifications from this specific application (typically a background usage OAUTH scope.) -2. The subscribing application MUST store the end user's tokens in order to call back for details once notified of changes. - -The final part of the sequence is the notification flow itself. - -Non-normative implementation guidance: A resource in the service changes and the service needs to run the following logic: - -1. Determine the set of users who have access to the resource, and could thus expect apps to receive notifications about it on their behalf. -2. See which of those users have consented to receiving notifications and from which apps. -3. See which apps have registered a firehose subscription. -4. Join 1, 2, 3 to produce the concrete set of notifications that must be sent to apps. - -It should be noted that the act of user consent and the act of setting up a firehose subscription could arrive in either order. -Services SHOULD send notifications with setup processed in either order. - -![Firehose subscription setup][websequencediagram-firehose-subscription-setup] - -For a per-user subscription, app registration is either manual or automated. -The call flow for a per-user subscription MUST follow the diagram below. -It shows the end user making use of one of the service's APIs, and again, the same two things MUST be stored: - -1. The service MUST store the end user's act of consent to receiving notifications from this specific application (typically a background usage OAUTH scope). -2. The subscribing application MUST store the end user's tokens in order to call back for details once notified of changes. - -In this case, the subscription is set up programmatically using the end-user's token from the subscribing application. -The app MUST store the ID of the registered subscription alongside the user tokens. - -Non normative implementation guidance: In the final part of the sequence, when an item of data in the service changes and the service needs to run the following logic: - -1. Find the set of subscriptions that correspond via resource to the data that changed. -2. For subscriptions created under an app+user token, send a notification to the app per subscription with the subscription ID and user id of the subscription-creator. -- For subscriptions created with an app only token, check that the owner of the changed data or any user that has visibility of the changed data has consented to notifications to the application, and if so send a set of notifications per user id to the app per subscription with the subscription ID. - - ![User subscription setup][websequencediagram-user-subscription-setup] - -### 15.5. Verifying subscriptions -When subscriptions change either programmatically or in response to change via administrative UI portals, the subscribing service needs to be protected from malicious or unexpected calls from services pushing potentially large volumes of notification traffic. - -For all subscriptions, whether firehose or per-user, services MUST send a verification request as part of creation or modification via portal UI or API request, before sending any other notifications. - -Verification requests MUST be of the following format as an HTTP/HTTPS POST to the subscription's _notificationUrl_. - -```http -POST https://{notificationUrl}?validationToken={randomString} -ClientState: clientOriginatedOpaqueToken (if provided by client on subscription-creation) -Content-Length: 0 -``` - -For the subscription to be set up, the application MUST respond with 200 OK to this request, with the _validationToken_ value as the sole entity body. -Note that if the _notificationUrl_ contains query parameters, the _validationToken_ parameter must be appended with an `&`. - -If any challenge request does not receive the prescribed response within 5 seconds of sending the request, the service MUST return an error, MUST NOT create the subscription, and MUST NOT send further requests or notifications to _notificationUrl_. - -Services MAY perform additional validations on URL ownership. - -### 15.6. Receiving notifications -Services SHOULD send notifications in response to service data changes that do not include details of the changes themselves, but include enough information for the subscribing application to respond appropriately to the following process: - -1. Applications MUST identify the correct cached OAuth token to use for a callback -2. Applications MAY look up any previous delta token for the relevant scope of change -3. Applications MUST determine the URL to call to perform the relevant query for the new state of the service, which MAY be a delta query. - -Services that are providing notifications that will be relayed to end users MAY choose to add more detail to notification packets in order to reduce incoming call load on their service. - Such services MUST be clear that notifications are not guaranteed to be delivered and may be lossy or out of order. - -Notifications MAY be aggregated and sent in batches. -Applications MUST be prepared to receive multiple events inside a single push notification. - -The service MUST send all Web Hook data notifications as POST requests. - -Services MUST allow for a 30-second timeout for notifications. -If a timeout occurs or the application responds with a 5xx response, then the service SHOULD retry the notification with exponential back-off. -All other responses will be ignored. - -The service MUST NOT follow 301/302 redirect requests. - -#### 15.6.1. Notification payload -The basic format for notification payloads is a list of events, each containing the id of the subscription whose referenced resources have changed, the type of change, the resource that should be consumed to identify the exact details of the change and sufficient identity information to look up the token required to call that resource. - -For a firehose subscription, a concrete example of this may look like: - -```json -{ - "value": [ - { - "subscriptionId": "32b8cbd6174ab18b", - "resource": "https://api.contoso.com/v1.0/users/user@contoso.com/files?$delta", - "userId" : "", - "tenantId" : "" - } - ] -} -``` - -For a per-user subscription, a concrete example of this may look like: - -```json -{ - "value": [ - { - "subscriptionId": "32b8cbd6174ab183", - "clientState": "clientOriginatedOpaqueToken", - "expirationDateTime": "2016-02-04T11:23Z", - "resource": "https://api.contoso.com/v1.0/users/user@contoso.com/files/$delta", - "userId" : "", - "tenantId" : "" - }, - { - "subscriptionId": "97b391179fa22", - "clientState ": "clientOriginatedOpaqueToken", - "expirationDateTime": "2016-02-04T11:23Z", - "resource": "https://api.contoso.com/v1.0/users/user@contoso.com/files/$delta", - "userId" : "", - "tenantId" : "" - } - ] -} -``` - -Following is a detailed description of the JSON payload. - -A notification item consists a top-level object that contains an array of events, each of which identified the subscription due to which this notification is being sent. - -Field | Description ------ | -------------------------------------------------------------------------------------------------- -value | Array of events that have been raised within the subscription’s scope since the last notification. - -Each item of the events array contains the following properties: - -Field | Description ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -subscriptionId | The id of the subscription due to which this notification has been sent.
    Services MUST provide the *subscriptionId* field. -clientState | Services MUST provide the *clientState* field if it was provided at subscription creation time. -expirationDateTime | Services MUST provide the *expirationDateTime* field if the subscription has one. -resource | Services MUST provide the resource field. This URL MUST be considered opaque by the subscribing application. In the case of a richer notification it MAY be subsumed by message content that implicitly contains the resource URL to avoid duplication.
    If a service is providing this data as part of a more detailed data packet, then it need not be duplicated. -userId | Services MUST provide this field for user-scoped resources. In the case of user-scoped resources, the unique identifier for the user should be used.
    In the case of resources shared between a specific set of users, multiple notifications must be sent, passing the unique identifier of each user.
    For tenant-scoped resources, the user id of the subscription should be used. -tenantId | Services that wish to support cross-tenant requests SHOULD provide this field. Services that provide notifications on tenant-scoped data MUST send this field. - -### 15.7. Managing subscriptions programmatically -For per-user subscriptions, an API MUST be provided to create and manage subscriptions. -The API must support at least the operations described here. - -#### 15.7.1. Creating subscriptions -A client creates a subscription by issuing a POST request against the subscriptions resource. -The subscription namespace is client-defined via the POST operation. - -``` -https://api.contoso.com/apiVersion/$subscriptions -``` - -The POST request contains a single subscription object to be created. -That subscription object has the following properties: - -Property Name | Required | Notes ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- -resource | Yes | Resource path to watch. -notificationUrl | Yes | The target web hook URL. -clientState | No | Opaque string passed back to the client on all notifications. Callers may choose to use this to provide tagging mechanisms. - -If the subscription was successfully created, the service MUST respond with the status code 201 CREATED and a body containing at least the following properties: - -Property Name | Required | Notes ------------------- | -------- | ------------------------------------------------------------------------------------------- -id | Yes | Unique ID of the new subscription that can be used later to update/delete the subscription. -expirationDateTime | No | Uses existing Microsoft REST API Guidelines defined time formats. - -Creation of subscriptions SHOULD be idempotent. -The combination of properties scoped to the auth token, provides a uniqueness constraint. - -Below is an example request using a User + Application principal to subscribe to notifications from a file: - -```http -POST https://api.contoso.com/files/v1.0/$subscriptions HTTP 1.1 -Authorization: Bearer {UserPrincipalBearerToken} - -{ - "resource": "http://api.service.com/v1.0/files/file1.txt", - "notificationUrl": "https://contoso.com/myCallbacks", - "clientState": "clientOriginatedOpaqueToken" -} -``` - -The service SHOULD respond to such a message with a response format minimally like this: - -```json -{ - "id": "32b8cbd6174ab18b", - "expirationDateTime": "2016-02-04T11:23Z" -} -``` - -Below is an example using an Application-Only principal where the application is watching all files to which it's authorized: - -```http -POST https://api.contoso.com/files/v1.0/$subscriptions HTTP 1.1 -Authorization: Bearer {ApplicationPrincipalBearerToken} - -{ - "resource": "All.Files", - "notificationUrl": "https://contoso.com/myCallbacks", - "clientState": "clientOriginatedOpaqueToken" -} -``` - -The service SHOULD respond to such a message with a response format minimally like this: - -```json -{ - "id": "8cbd6174abb391179", - "expirationDateTime": "2016-02-04T11:23Z" -} -``` - -#### 15.7.2. Updating subscriptions -Services MAY support amending subscriptions. - To update the properties of an existing subscription, clients use PATCH requests providing the ID and the properties that need to change. -Omitted properties will retain their values. -To delete a property, assign a value of JSON null to it. - -As with creation, subscriptions are individually managed. - -The following request changes the notification URL of an existing subscription: - -```http -PATCH https://api.contoso.com/files/v1.0/$subscriptions/{id} HTTP 1.1 -Authorization: Bearer {UserPrincipalBearerToken} - -{ - "notificationUrl": "https://contoso.com/myNewCallback" -} -``` - -If the PATCH request contains a new _notificationUrl_, the server MUST perform validation on it as described above. -If the new URL fails to validate, the service MUST fail the PATCH request and leave the subscription in its previous state. - -The service MUST return an empty body and `204 No Content` to indicate a successful patch. - -The service MUST return an error body and status code if the patch failed. - -The operation MUST succeed or fail atomically. - -#### 15.7.3. Deleting subscriptions -Services MUST support deleting subscriptions. -Existing subscriptions can be deleted by making a DELETE request against the subscription resource: - -```http -DELETE https://api.contoso.com/files/v1.0/$subscriptions/{id} HTTP 1.1 -Authorization: Bearer {UserPrincipalBearerToken} -``` - -As with update, the service MUST return `204 No Content` for a successful delete, or an error body and status code to indicate failure. - -#### 15.7.4. Enumerating subscriptions -To get a list of active subscriptions, clients issue a GET request against the subscriptions resource using a User + Application or Application-Only bearer token: - -```http -GET https://api.contoso.com/files/v1.0/$subscriptions HTTP 1.1 -Authorization: Bearer {UserPrincipalBearerToken} -``` - -The service MUST return a format as below using a User + Application principal bearer token: - -```json -{ - "value": [ - { - "id": "32b8cbd6174ab18b", - "resource": " http://api.contoso.com/v1.0/files/file1.txt", - "notificationUrl": "https://contoso.com/myCallbacks", - "clientState": "clientOriginatedOpaqueToken", - "expirationDateTime": "2016-02-04T11:23Z" - } - ] -} -``` - -An example that may be returned using Application-Only principal bearer token: - -```json -{ - "value": [ - { - "id": "6174ab18bfa22", - "resource": "All.Files ", - "notificationUrl": "https://contoso.com/myCallbacks", - "clientState": "clientOriginatedOpaqueToken", - "expirationDateTime": "2016-02-04T11:23Z" - } - ] -} -``` - -### 15.8. Security -All service URLs must be HTTPS (that is, all inbound calls MUST be HTTPS). Services that deal with Web Hooks MUST accept HTTPS. - -We recommend that services that allow client defined Web Hook Callback URLs SHOULD NOT transmit data over HTTP. -This is because information can be inadvertently exposed via client, network, server logs and other mechanisms. - -However, there are scenarios where the above recommendations cannot be followed due to client endpoint or software limitations. -Consequently, services MAY allow web hook URLs that are HTTP. - -Furthermore, services that allow client defined HTTP web hooks callback URLs SHOULD be compliant with privacy policy specified by engineering leadership. -This will typically include recommending that clients prefer SSL connections and adhere to special precautions to ensure that logs and other service data collection are properly handled. - -For example, services may not want to require developers to generate certificates to onboard. -Services might only enable this on test accounts. - -## 16. Unsupported requests -RESTful API clients MAY request functionality that is currently unsupported. -RESTful APIs MUST respond to valid but unsupported requests consistent with this section. - -### 16.1. Essential guidance -RESTful APIs will often choose to limit functionality that can be performed by clients. -For instance, auditing systems allow records to be created but not modified or deleted. -Similarly, some APIs will expose collections but require or otherwise limit filtering and ordering criteria, or MAY not support client-driven pagination. - -### 16.2. Feature allow list -If a service does not support any of the below API features, then an error response MUST be provided if the feature is requested by a caller. -The features are: -- Key Addressing in a collection, such as: `https://api.contoso.com/v1.0/people/user1@contoso.com` -- Filtering a collection by a property value, such as: `https://api.contoso.com/v1.0/people?$filter=name eq 'david'` -- Filtering a collection by range, such as: `http://api.contoso.com/v1.0/people?$filter=hireDate ge 2014-01-01 and hireDate le 2014-12-31` -- Client-driven pagination via $top and $skip, such as: `http://api.contoso.com/v1.0/people?$top=5&$skip=2` -- Sorting by $orderBy, such as: `https://api.contoso.com/v1.0/people?$orderBy=name desc` -- Providing $delta tokens, such as: `https://api.contoso.com/v1.0/people?$delta` - -#### 16.2.1. Error response -Services MUST provide an error response if a caller requests an unsupported feature found in the feature allow list. -The error response MUST be an HTTP status code from the 4xx series, indicating that the request cannot be fulfilled. -Unless a more specific error status is appropriate for the given request, services SHOULD return "400 Bad Request" and an error payload conforming to the error response guidance provided in the Microsoft REST API Guidelines. -Services SHOULD include enough detail in the response message for a developer to determine exactly what portion of the request is not supported. - -Example: - -```http -GET https://api.contoso.com/v1.0/people?$orderBy=name HTTP/1.1 -Accept: application/json -``` - -```http -HTTP/1.1 400 Bad Request -Content-Type: application/json - -{ - "error": { - "code": "ErrorUnsupportedOrderBy", - "message": "Ordering by name is not supported." - } -} -``` - -## 17. Naming guidelines -### 17.1. Approach -Naming policies should aid developers in discovering functionality without having to constantly refer to documentation. -Use of common patterns and standard conventions greatly aids developers in correctly guessing common property names and meanings. -Services SHOULD use verbose naming patterns and SHOULD NOT use abbreviations other than acronyms that are the dominant mode of expression in the domain being represented by the API, (e.g. Url). - -### 17.2. Casing -- Acronyms SHOULD follow the casing conventions as though they were regular words (e.g. Url). -- All identifiers including namespaces, entityTypes, entitySets, properties, actions, functions and enumeration values SHOULD use lowerCamelCase. -- HTTP headers are the exception and SHOULD use standard HTTP convention of Capitalized-Hyphenated-Terms. - -### 17.3. Names to avoid -Certain names are so overloaded in API domains that they lose all meaning or clash with other common usages in domains that cannot be avoided when using REST APIs, such as OAUTH. -Services SHOULD NOT use the following names: -- Context -- Scope -- Resource - -### 17.4. Forming compound names -- Services SHOULD avoid using articles such as 'a', 'the', 'of' unless needed to convey meaning. - - e.g. names such as aUser, theAccount, countOfBooks SHOULD NOT be used, rather user, account, bookCount SHOULD be preferred. -- Services SHOULD add a type to a property name when not doing so would cause ambiguity about how the data is represented or would cause the service not to use a common property name. -- When adding a type to a property name, services MUST add the type at the end, e.g. createdDateTime. - -### 17.5. Identity properties -- Services MUST use string types for identity properties. -- For OData services, the service MUST use the OData @id property to represent the canonical identifier of the resource. -- Services MAY use the simple 'id' property to represent a local or legacy primary key value for a resource. -- Services SHOULD use the name of the relationship postfixed with 'Id' to represent a foreign key to another resource, e.g. subscriptionId. - - The content of this property SHOULD be the canonical ID of the referenced resource. - -### 17.6. Date and time properties - -- For properties requiring both date and time, services MUST use the suffix 'DateTime'. -- For properties requiring only date information without specifying time, services MUST use the suffix 'Date', e.g. birthDate. -- For properties requiring only time information without specifying date, services MUST use the suffix 'Time', e.g. appointmentStartTime. - -### 17.7. Name properties -- For the overall name of a resource typically shown to users, services MUST use the property name 'displayName'. -- Services MAY use other common naming properties, e.g. givenName, surname, signInName. - -### 17.8. Collections and counts -- Services MUST name collections as plural nouns or plural noun phrases using correct English. -- Services MAY use simplified English for nouns that have plurals not in common verbal usage. - - e.g. schemas MAY be used instead of schemata. -- Services MUST name counts of resources with a noun or noun phrase suffixed with 'Count'. - -### 17.9. Common property names -Where services have a property, whose data matches the names below, the service MUST use the name from this table. -This table will grow as services add terms that will be more commonly used. -Service owners adding such terms SHOULD propose additions to this document. - -| | -|------------- | - attendees | - body | - createdDateTime | - childCount | - children | - contentUrl | - country | - createdBy | - displayName | - errorUrl | - eTag | - event | - expirationDateTime | - givenName | - jobTitle | - kind | - id | - lastModifiedDateTime | - location | - memberOf | - message | - name | - owner | - people | - person | - postalCode | - photo | - preferredLanguage | - properties | - signInName | - surname | - tags | - userPrincipalName | - webUrl | - -## 18. Appendix -### 18.1. Sequence diagram notes -All sequence diagrams in this document are generated using the [WebSequenceDiagrams.com](https://www.websequencediagrams.com/). To generate them, paste the text below into the web tool. - -#### 18.1.1. Push notifications, per user flow - -``` -=== Begin Text === -note over Developer, Automation, App Server: - An App Developer like MovieMaker - Wants to integrate with primary service like Dropbox -end note -note over DB Portal, DB App Registration, DB Notifications, DB Auth, DB Service: The primary service like Dropbox -note over Client: The end users' browser or installed app - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Manual App Registration - - -Developer <--> DB Portal : Login into Portal, App Registration UX -DB Portal -> +DB App Registration: App Name etc. -note over DB App Registration: Confirm Portal Access Token - -DB App Registration -> -DB Portal: App ID -DB Portal <--> App Server: Developer copies App ID - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Manual Notification Registration - -Developer <--> DB Portal: webhook registration UX -DB Portal -> +DB Notifications: Register: App Server webhook URL, Scope, App ID -Note over DB Notifications : Confirm Portal Access Token -DB Notifications -> -DB Portal: notification ID -DB Portal --> App Server : Developer may copy notification ID - - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Client Authorization - -Client -> +App Server : Request access to DB protected information -App Server -> -Client : Redirect to DB Authorization endpoint with authorization request -Client -> +DB Auth : Redirected authorization request -Client <--> DB Auth : Authorization UX -DB Auth -> -Client : Redirect back to App Server with code -Client -> +App Server : Redirect request back to access server with access code -App Server -> +DB Auth : Request tokens with access code -note right of DB Service: Cache that this User ID provided access to App ID -DB Auth -> -App Server : Response with access, refresh, and ID tokens -note right of App Server : Cache tokens by user ID -App Server -> -Client : Return information to client - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Notification Flow - -Client <--> DB Service: Changes to user data - typical via interacting with App Server via Client -DB Service -> App Server : Notification with notification ID and user ID -App Server -> +DB Service : Request changed information with cached access tokens and "since" token -note over DB Service: Confirm User Access Token -DB Service -> -App Server : Response with data and new "since" token -note right of App Server: Update status and cache new "since" token -=== End Text === -``` - -#### 18.1.2. Push notifications, firehose flow - -``` -=== Begin Text === -note over Developer, Automation, App Server: - An App Developer like MovieMaker - Wants to integrate with primary service like Dropbox -end note -note over DB Portal, DB App Registration, DB Notifications, DB Auth, DB Service: The primary service like Dropbox -note over Client: The end users' browser or installed app - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : App Registration - -alt Automated app registration - Developer <--> Automation: Configure - Automation -> +DB App Registration: App Name etc. - note over DB App Registration: Confirm App Access Token - DB App Registration -> -Automation: App ID, App Secret - Automation --> App Server : Embed App ID, App Secret -else Manual app registration - Developer <--> DB Portal : Login into Portal, App Registration UX - DB Portal -> +DB App Registration: App Name etc. - note over DB App Registration: Confirm Portal Access Token - - DB App Registration -> -DB Portal: App ID - DB Portal <--> App Server: Developer copies App ID -end - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Client Authorization - -Client -> +App Server : Request access to DB protected information -App Server -> -Client : Redirect to DB Authorization endpoint with authorization request -Client -> +DB Auth : Redirected authorization request -Client <--> DB Auth : Authorization UX -DB Auth -> -Client : Redirect back to App Server with code -Client -> +App Server : Redirect request back to access server with access code -App Server -> +DB Auth : Request tokens with access code -note right of DB Service: Cache that this User ID provided access to App ID -DB Auth -> -App Server : Response with access, refresh, and ID tokens -note right of App Server : Cache tokens by user ID -App Server -> -Client : Return information to client - - - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Notification Registration - -App Server->+DB Notifications: Register: App server webhook URL, Scope, App ID -note over DB Notifications : Confirm User Access Token -DB Notifications -> -App Server: notification ID -note right of App Server : Cache the Notification ID and User Access Token - - - -note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Notification Flow - -Client <--> DB Service: Changes to user data - typical via interacting with App Server via Client -DB Service -> App Server : Notification with notification ID and user ID -App Server -> +DB Service : Request changed information with cached access tokens and "since" token -note over DB Service: Confirm User Access Token -DB Service -> -App Server : Response with data and new "since" token -note right of App Server: Update status and cache new "since" token - - - -=== End Text === -``` -[fielding]: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm -[IANA-headers]: https://www.iana.org/assignments/message-headers/message-headers.xhtml -[rfc-2119]: https://tools.ietf.org/html/rfc2119 -[rfc7231-7-1-1-1]: https://tools.ietf.org/html/rfc7231#section-7.1.1.1 -[rfc-7230-3-1-1]: https://tools.ietf.org/html/rfc7230#section-3.1.1 -[rfc-7231]: https://tools.ietf.org/html/rfc7231 -[rest-in-practice]: https://www.amazon.com/REST-Practice-Hypermedia-Systems-Architecture/dp/0596805829/ -[rest-on-wikipedia]: https://en.wikipedia.org/wiki/Representational_state_transfer -[rfc-5789]: https://tools.ietf.org/html/rfc5789 -[rfc-5988]: https://tools.ietf.org/html/rfc5988 -[rfc-3339]: https://tools.ietf.org/html/rfc3339 -[rfc-5322-3-3]: https://tools.ietf.org/html/rfc5322#section-3.3 -[cors-preflight]: https://www.w3.org/TR/cors/#resource-preflight-requests -[rfc-3864]: https://tools.ietf.org/html/rfc3864 -[odata-json-annotations]: https://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Instance_Annotations -[cors]: https://www.w3.org/TR/access-control/ -[cors-user-credentials]: https://www.w3.org/TR/access-control/#user-credentials -[cors-simple-headers]: https://www.w3.org/TR/access-control/#simple-header -[rfc-4627]: https://tools.ietf.org/html/rfc4627 -[iso-8601]: https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 -[clr-time]: https://msdn.microsoft.com/en-us/library/System.DateTime(v=vs.110).aspx -[ecmascript-time]: https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.1 -[ole-date]: https://docs.microsoft.com/en-us/windows/desktop/api/oleauto/nf-oleauto-varianttimetosystemtime -[ticks-time]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx -[unix-time]: https://msdn.microsoft.com/en-us/library/1f4c8f33.aspx -[windows-time]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx -[excel-time]: https://support.microsoft.com/kb/214326?wa=wsignin1.0 -[wikipedia-iso8601-durations]: https://en.wikipedia.org/wiki/ISO_8601#Durations -[wikipedia-iso8601-intervals]: https://en.wikipedia.org/wiki/ISO_8601#Time_intervals -[wikipedia-iso8601-repeatingintervals]: https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals -[principle-of-least-astonishment]: https://en.wikipedia.org/wiki/Principle_of_least_astonishment -[odata-breaking-changes]: https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209 -[websequencediagram-firehose-subscription-setup]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=bm90ZSBvdmVyIERldmVsb3BlciwgQXV0b21hdGlvbiwgQXBwIFNlcnZlcjogCiAgICAgQW4AEAUAJwkgbGlrZSBNb3ZpZU1ha2VyACAGV2FudHMgdG8gaW50ZWdyYXRlIHdpdGggcHJpbWFyeSBzZXJ2aWNlADcGRHJvcGJveAplbmQgbm90ZQoAgQwLQiBQb3J0YWwsIERCAIEJBVJlZ2lzdHIAgRkHREIgTm90aWZpYwCBLAVzACEGdXRoACsFUwBgBjogVGhlAF0eAIF_CkNsaWVudAAtBmVuZCB1c2VycycgYnJvd3NlciBvciBpbnN0YWxsZWQgYXBwCgCBIQwAgiQgAIFABQCBIS8AgQoGIDogTWFudWFsAIFzEQoKCgCDAgo8LS0-AIIqCiA6IExvZ2luIGludG8Agj8JAII1ECBVWCAKACoKLT4gKwCCWBM6AIQGBU5hbWUgZXRjLgCDFQ4AGxJDb25maXJtAIEBCEFjY2VzcyBUb2tlbgoKAIM3EyAtPiAtAINkCQBnBklEAIEMCwCBVQUAhQIMAIR3CmNvcGllcwArCACCIHAAhHMMAIMKDwCDABg6IHdlYmhvb2sgcgCCeg4AgnUSAIVQDToAhXYHZXIAgwgGAIcTBgBECVVSTCwgU2NvcGUAhzIGSUQKTgCGPQwAhhwNIACDBh4AHhEAgxEPbgCBagwAgxwNAIMaDiAAgx0MbWF5IGNvcHkALREAhVtqAIZHB0F1dGhvcml6AIY7BwCGXQctPiArAIEuDVJlcXVlc3QgYQCFOQZ0byBEQiBwcm90ZWN0ZWQgaW5mb3IAiiQGCgCDBQstPiAtAIctCVJlZGlyZWN0ADYHAGwNIGVuZHBvaW50AIoWBmEADw1yAHYGAIEQDACJVAcASwtlZAAYHgCICAgAMAcAcA4AhGoGAE0FAIEdFmJhY2sgdG8AhF8NaXRoIGNvZGUAghoaaQCBagcAgToHAD0JAII-B3MAPgsAglEHAEsFAIIzDgCBXw0Agn8GdG9rZW5zACcSAI0_BXJpZ2h0IG9mAItpDUNhY2hlIHRoYXQgdGhpcyBVc2VyIElEIHByb3ZpZGVkAINNCwCIZgoAggcJAIN7D3Nwb25zAI0_BwCECgYsIHJlZnJlc2gsIGFuZCBJRACBHAcAgQMPAIYADQCBDAcAgUUGYnkAjFkFIElEAIQkG3R1cm4AhF4MIHRvIGMAjR8FAIwRagCJVw1GbG93AIYqCQCMaQgAgmoKaGFuZ2UAj3YFAIFXBWRhdGEgLSB0eXBpY2FsIHZpYQCQDgVyYWN0aW5nAJAPBgCJQQt2aWEAjnsHCgCPNgogAIhDEACKZw0AkFMFAIkBDwCDDAUAgkYWKwBNCwCHWApjAIEyBQCHRg0AhWUHYWNoAIQeDACEfwVhbmQgInNpbmNlIgCFEQYAkSQOAIR3CgCNfwcAhHQFAIpQEACBUgsAhFAcAII8BWFuZCBuZXcAYRQAhFUTOiBVcGRhdGUgc3RhdHUAgSkGAIFDBQAxEwoKCg&s=mscgen -[websequencediagram-user-subscription-setup]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=bm90ZSBvdmVyIERldmVsb3BlciwgQXV0b21hdGlvbiwgQXBwIFNlcnZlcjogCiAgICAgQW4AEAUAJwkgbGlrZSBNb3ZpZU1ha2VyACAGV2FudHMgdG8gaW50ZWdyYXRlIHdpdGggcHJpbWFyeSBzZXJ2aWNlADcGRHJvcGJveAplbmQgbm90ZQoAgQwLQiBQb3J0YWwsIERCAIEJBVJlZ2lzdHIAgRkHREIgTm90aWZpYwCBLAVzACEGdXRoACsFUwBgBjogVGhlAF0eAIF_CkNsaWVudAAtBmVuZCB1c2VycycgYnJvd3NlciBvciBpbnN0YWxsZWQgYXBwCgCBIQwAgiQgAIFABQCBIS8AgQoGIDoAgWwRCgphbHQAgyUIAIEHBiByABQMICAAgxsLPC0tPgCDTws6IENvbmZpZ3VyZQogIACDaAsgLT4gKwCCWBMAegZOYW1lIGV0Yy4AhAgFAIMaDQAfEgBdBXJtAIQ_BUFjY2VzcyBUb2tlAIETBgCDOxIgLT4gLQCBFgxBcHAgSUQAhHwIY3JldACBGxAtPgCFFgsgOiBFbWJlZAAkFGVsc2UgTWFudWFsAIIEJACEbQkgOiBMb2dpbiBpbnRvAIUBCQCBKRFVWACGGAUALQoAgh8mAIIZKwCBCAcAgjoNAIIsHACGLwkAgj8IAIESDgCECAYAh1ELAIdFCmNvcGllcwAuCGVuZACEeGoAhWQHQXV0aG9yaXoAhV8HAIV6By0-ICsAg2ANUmVxdWVzdCBhAIRVBnRvIERCIHByb3RlY3RlZCBpbmZvcgCJQQYKAIQaCy0-IC0AhkoJUmVkaXJlY3QANgcAbA0gZW5kcG9pbnQAiTMGYQAPDXIAdgYAgRAMAIhxBwBLC2VkABgeAIRjCAAwB0EAcQxVWAoASQgAgRwWYmFjayB0bwCFdAwAilwFY29kZQCCGRppAIFpBwCBOQcAPQkAgj0HcwA-CwCCUAcASwUAgjIOAIFeDQCCfgZ0b2tlbnMAJxIAjFsFcmlnaHQgb2YAiwUNQ2FjaGUgdGhhdCB0aGlzIFVzZXIgSUQgcHJvdmlkZWQAg0wLAIU6BwCCBAwAg3oPc3BvbnMAjFsHAIQJBiwgcmVmcmVzaCwgYW5kIElEAIEcBwCBAw8AiDENAIEMBwCBRQZieQCLdQUgSUQAhCMbdHVybgCEXQwgdG8gYwCMOwUKCgCLL2oAjXUMAIwTDwCPNQotPisAjhwQOgCORQdlcgCMVwYAg3YIZWJob29rIFVSTCwgU2NvcGUAkAEGSUQAjwoOAI5rDSAAi2UKAINFBQCLYw0AHBEAgzUOOiBuAIE2DABgCACDCB1oZQCBaQ5JRACDYwUAahIAghB4RmxvdwCJMwkAjE0IAIV0CmhhbmdlAJIcBQCEYQVkYXRhIC0gdHlwaWNhbCB2aWEAkjQFcmFjdGluZwCSNQYAjV8LdmlhAJEhBwoAkVwKIACNfhAAhAsNAJJ5BQCCWQ8AhhYFAIVQFisATQsAimEKYwCBMgUAik8NAIhvB2FjaACHKAwAiAkFYW5kICJzaW5jZSIAiBsGAJNKDgCIAQoAhB0cAIFSCwCHWhwAgjwFYW5kIG5ldwBhFACHXxM6IFVwZGF0ZSBzdGF0dQCBKQYAgUMFADETCgoK&s=mscgen diff --git a/README.md b/README.md index 8c88ec68..731da41c 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,19 @@ - # NOTICE TO READERS - - ## Guidance for Azure service teams -Azure service teams should use companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information please refer to the [README](./azure/README.md) in the Azure folder. - -## Guidance for Microsoft Graph service teams - Microsoft Graph service teams should reference the companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. +# Microsoft REST API Guidelines +Thank you for your interest in the Microsoft REST API Guidelines. If you have landed here, you're probably interested in learning about APIs. If so, you are in the right place! +We publish these guidelines here with the aim of fostering dialogue and learning in the API community at large. We further hope that these guidelines might encourage other organizations to create guidelines that are appropriate for them and in turn, if they're able, to publish theirs. -In the process of building many of Microsoft's highest scale services, the Microsoft Graph team found the Microsoft API guidelines tremendously useful as a baseline. However, there are several areas where we need to provide more clarity on how developers should describe their APIs. The companion document, [Graph REST API Guidelines](./graph/GuidelinesGraph.md) is a set of amendments and clarifications for Microsoft Graph that act as further reading. Recognizing that two documents is a lot for a new API designer to absorb, our plan is to follow the approach Azure have taken and roll out guidelines for Microsoft Graph into a single consolidated document. +### Guidance for Azure service teams +Azure service teams should reference the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information, please refer to the [README](./azure/README.md) in the Azure folder. ---- - -## Microsoft REST API Guidelines -The [Microsoft REST API Guidelines](Guidelines.md) are Microsoft's internal company-wide REST API design guidelines. -Teams at Microsoft typically reference this document when setting API design policy. -They may additionally create documents specific to their team, adding further guidance or making adjustments as appropriate to their circumstances. - -We publish these guidelines here with the aim of fostering dialogue and learning in the API community at large. -We further hope that these guidelines may encourage other organizations to create guidelines that are appropriate for them and in turn, if they are able, to publish theirs. +### Guidance for Microsoft Graph service teams +Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provide a refined set of guidance targeted specifically for Microsoft Graph services. [![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) ## Code of Conduct -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +This project adopts the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## This repository -This repository contains a collection of documents and related materials supporting the overall Microsoft REST API Guidelines initiative. To contribute to this repository, please see the [contribution guidelines][contribution-guidance]. +This repository contains a collection of documents and related materials supporting the overall Microsoft REST API Guidelines initiative. To contribute to this repository, see the [contribution guidelines][contribution-guidance]. [contribution-guidance]: CONTRIBUTING.md diff --git a/graph/Guidelines-deprecated.md b/graph/Guidelines-deprecated.md new file mode 100644 index 00000000..0cc804ad --- /dev/null +++ b/graph/Guidelines-deprecated.md @@ -0,0 +1,2448 @@ +# Microsoft REST API Guidelines +> # NOTICE TO READERS +> This document has been deprecated and has been merged with [Microsoft Graph REST API Guidelines](./graph/Guidelines_deprecated.md). Please refer to the notes below for the latest guidance. +> +> ## **Guidance for Azure service teams** +> Azure service teams should use the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information, see the [README](./azure/README.md) in the Azure folder. +> ## **Guidance for Microsoft Graph service teams** +> Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. +> +--- +This are Microsoft's internal company-wide REST API design guidelines. +Teams at Microsoft typically reference this document when setting API design policy. +They may additionally create documents specific to their team, adding further guidance or making adjustments as appropriate to their circumstances. + +## Microsoft REST API Guidelines Working Group + +Name | Name | Name | +---------------------------- | -------------------------------------- | ---------------------------------------- +Dave Campbell (CTO C+E) | Rick Rashid (CTO ASG) | John Shewchuk (Technical Fellow, TED HQ) +Mark Russinovich (CTO Azure) | Steve Lucco (Technical Fellow, DevDiv) | Murali Krishnaprasad (Azure App Plat) +Rob Howard (ASG) | Peter Torr (OSG) | Chris Mullins (ASG) + +
    +Document editors: John Gossman (C+E), Chris Mullins (ASG), Gareth Jones (ASG), Rob Dolin (C+E), Mark Stafford (C+E)
    +
    + +# Microsoft REST API Guidelines + +## 1. Abstract +The Microsoft REST API Guidelines, as a design principle, encourages application developers to have resources accessible to them via a RESTful HTTP interface. +To provide the smoothest possible experience for developers on platforms following the Microsoft REST API Guidelines, REST APIs SHOULD follow consistent design guidelines to make using them easy and intuitive. + +This document establishes the guidelines Microsoft REST APIs SHOULD follow so RESTful interfaces are developed consistently. + +## 2. Table of contents + + +- [Microsoft REST API Guidelines](#microsoft-rest-api-guidelines) + - [Microsoft REST API Guidelines Working Group](#microsoft-rest-api-guidelines-working-group) +- [Microsoft REST API Guidelines](#microsoft-rest-api-guidelines-1) + - [1. Abstract](#1-abstract) + - [2. Table of contents](#2-table-of-contents) + - [3. Introduction](#3-introduction) + - [3.1. Recommended reading](#31-recommended-reading) + - [4. Interpreting the guidelines](#4-interpreting-the-guidelines) + - [4.1. Application of the guidelines](#41-application-of-the-guidelines) + - [4.2. Guidelines for existing services and versioning of services](#42-guidelines-for-existing-services-and-versioning-of-services) + - [4.3. Requirements language](#43-requirements-language) + - [4.4. License](#44-license) + - [5. Taxonomy](#5-taxonomy) + - [5.1. Errors](#51-errors) + - [5.2. Faults](#52-faults) + - [5.3. Latency](#53-latency) + - [5.4. Time to complete](#54-time-to-complete) + - [5.5. Long running API faults](#55-long-running-api-faults) + - [6. Client guidance](#6-client-guidance) + - [6.1. Ignore rule](#61-ignore-rule) + - [6.2. Variable order rule](#62-variable-order-rule) + - [6.3. Silent fail rule](#63-silent-fail-rule) + - [7. Consistency fundamentals](#7-consistency-fundamentals) + - [7.1. URL structure](#71-url-structure) + - [7.2. URL length](#72-url-length) + - [7.3. Canonical identifier](#73-canonical-identifier) + - [7.4. Supported methods](#74-supported-methods) + - [7.4.1. POST](#741-post) + - [7.4.2. PATCH](#742-patch) + - [7.4.3. Creating resources via PATCH (UPSERT semantics)](#743-creating-resources-via-patch-upsert-semantics) + - [7.4.4. Options and link headers](#744-options-and-link-headers) + - [7.5. Standard request headers](#75-standard-request-headers) + - [7.6. Standard response headers](#76-standard-response-headers) + - [7.7. Custom headers](#77-custom-headers) + - [7.8. Specifying headers as query parameters](#78-specifying-headers-as-query-parameters) + - [7.9. PII parameters](#79-pii-parameters) + - [7.10. Response formats](#710-response-formats) + - [7.10.1. Clients-specified response format](#7101-clients-specified-response-format) + - [7.10.2. Error condition responses](#7102-error-condition-responses) + - [ErrorResponse : Object](#errorresponse--object) + - [Error : Object](#error--object) + - [InnerError : Object](#innererror--object) + - [Examples](#examples) + - [7.11. HTTP Status Codes](#711-http-status-codes) + - [7.12. Client library optional](#712-client-library-optional) + - [8. CORS](#8-cors) + - [8.1. Client guidance](#81-client-guidance) + - [8.1.1. Avoiding preflight](#811-avoiding-preflight) + - [8.2. Service guidance](#82-service-guidance) + - [9. Collections](#9-collections) + - [9.1. Item keys](#91-item-keys) + - [9.2. Serialization](#92-serialization) + - [9.3. Collection URL patterns](#93-collection-url-patterns) + - [9.3.1. Nested collections and properties](#931-nested-collections-and-properties) + - [9.4. Big collections](#94-big-collections) + - [9.5. Changing collections](#95-changing-collections) + - [9.6. Sorting collections](#96-sorting-collections) + - [9.6.1. Interpreting a sorting expression](#961-interpreting-a-sorting-expression) + - [9.7. Filtering](#97-filtering) + - [9.7.1. Filter operations](#971-filter-operations) + - [9.7.2. Operator examples](#972-operator-examples) + - [9.7.3. Operator precedence](#973-operator-precedence) + - [9.8. Pagination](#98-pagination) + - [9.8.1. Server-driven paging](#981-server-driven-paging) + - [9.8.2. Client-driven paging](#982-client-driven-paging) + - [9.8.3. Additional considerations](#983-additional-considerations) + - [9.9. Compound collection operations](#99-compound-collection-operations) + - [9.10. Empty Results](#910-empty-results) + - [10. Delta queries](#10-delta-queries) + - [10.1. Delta links](#101-delta-links) + - [10.2. Entity representation](#102-entity-representation) + - [10.3. Obtaining a delta link](#103-obtaining-a-delta-link) + - [10.4. Contents of a delta link response](#104-contents-of-a-delta-link-response) + - [10.5. Using a delta link](#105-using-a-delta-link) + - [11. JSON standardizations](#11-json-standardizations) + - [11.1. JSON formatting standardization for primitive types](#111-json-formatting-standardization-for-primitive-types) + - [11.2. Guidelines for dates and times](#112-guidelines-for-dates-and-times) + - [11.2.1. Producing dates](#1121-producing-dates) + - [11.2.2. Consuming dates](#1122-consuming-dates) + - [11.2.3. Compatibility](#1123-compatibility) + - [11.3. JSON serialization of dates and times](#113-json-serialization-of-dates-and-times) + - [11.3.1. The `DateLiteral` format](#1131-the-dateliteral-format) + - [11.3.2. Commentary on date formatting](#1132-commentary-on-date-formatting) + - [11.4. Durations](#114-durations) + - [11.5. Intervals](#115-intervals) + - [11.6. Repeating intervals](#116-repeating-intervals) + - [12. Versioning](#12-versioning) + - [12.1. Versioning formats](#121-versioning-formats) + - [12.1.1. Group versioning](#1211-group-versioning) + - [Examples of group versioning](#examples-of-group-versioning) + - [12.2. When to version](#122-when-to-version) + - [12.3. Definition of a breaking change](#123-definition-of-a-breaking-change) + - [13. Long running operations](#13-long-running-operations) + - [13.1. Resource based long running operations (RELO)](#131-resource-based-long-running-operations-relo) + - [13.2. Stepwise long running operations](#132-stepwise-long-running-operations) + - [13.2.1. PUT](#1321-put) + - [13.2.2. POST](#1322-post) + - [13.2.3. POST, hybrid model](#1323-post-hybrid-model) + - [13.2.4. Operations resource](#1324-operations-resource) + - [13.2.5. Operation resource](#1325-operation-resource) + - [Percent complete](#percent-complete) + - [Target resource location](#target-resource-location) + - [13.2.6. Operation tombstones](#1326-operation-tombstones) + - [13.2.7. The typical flow, polling](#1327-the-typical-flow-polling) + - [Example of the typical flow, polling](#example-of-the-typical-flow-polling) + - [13.2.8. The typical flow, push notifications](#1328-the-typical-flow-push-notifications) + - [Example of the typical flow, push notifications existing subscription](#example-of-the-typical-flow-push-notifications-existing-subscription) + - [13.2.9. Retry-After](#1329-retry-after) + - [13.3. Retention policy for operation results](#133-retention-policy-for-operation-results) + - [14. Throttling, Quotas, and Limits](#14-throttling-quotas-and-limits) + - [14.1. Principles](#141-principles) + - [14.2. Return Codes (429 vs 503)](#142-return-codes-429-vs-503) + - [14.3. Retry-After and RateLimit Headers](#143-retry-after-and-ratelimit-headers) + - [14.4. Service Guidance](#144-service-guidance) + - [14.4.1. Responsiveness](#1441-responsiveness) + - [14.4.2. Rate Limits and Quotas](#1442-rate-limits-and-quotas) + - [14.4.3. Overloaded services](#1443-overloaded-services) + - [14.4.4. Example Response](#1444-example-response) + - [14.5. Caller Guidance](#145-caller-guidance) + - [14.6. Handling callers that ignore Retry-After headers](#146-handling-callers-that-ignore-retry-after-headers) + - [15. Push notifications via webhooks](#15-push-notifications-via-webhooks) + - [15.1. Scope](#151-scope) + - [15.2. Principles](#152-principles) + - [15.3. Types of subscriptions](#153-types-of-subscriptions) + - [15.4. Call sequences](#154-call-sequences) + - [15.5. Verifying subscriptions](#155-verifying-subscriptions) + - [15.6. Receiving notifications](#156-receiving-notifications) + - [15.6.1. Notification payload](#1561-notification-payload) + - [15.7. Managing subscriptions programmatically](#157-managing-subscriptions-programmatically) + - [15.7.1. Creating subscriptions](#1571-creating-subscriptions) + - [15.7.2. Updating subscriptions](#1572-updating-subscriptions) + - [15.7.3. Deleting subscriptions](#1573-deleting-subscriptions) + - [15.7.4. Enumerating subscriptions](#1574-enumerating-subscriptions) + - [15.8. Security](#158-security) + - [16. Unsupported requests](#16-unsupported-requests) + - [16.1. Essential guidance](#161-essential-guidance) + - [16.2. Feature allow list](#162-feature-allow-list) + - [16.2.1. Error response](#1621-error-response) + - [17. Naming guidelines](#17-naming-guidelines) + - [17.1. Approach](#171-approach) + - [17.2. Casing](#172-casing) + - [17.3. Names to avoid](#173-names-to-avoid) + - [17.4. Forming compound names](#174-forming-compound-names) + - [17.5. Identity properties](#175-identity-properties) + - [17.6. Date and time properties](#176-date-and-time-properties) + - [17.7. Name properties](#177-name-properties) + - [17.8. Collections and counts](#178-collections-and-counts) + - [17.9. Common property names](#179-common-property-names) + - [18. Appendix](#18-appendix) + - [18.1. Sequence diagram notes](#181-sequence-diagram-notes) + - [18.1.1. Push notifications, per user flow](#1811-push-notifications-per-user-flow) + - [18.1.2. Push notifications, firehose flow](#1812-push-notifications-firehose-flow) + + + +## 3. Introduction +Developers access most Microsoft Cloud Platform resources via HTTP interfaces. +Although each service typically provides language-specific frameworks to wrap their APIs, all of their operations eventually boil down to HTTP requests. +Microsoft must support a wide range of clients and services and cannot rely on rich frameworks being available for every development environment. +Thus, a goal of these guidelines is to ensure Microsoft REST APIs can be easily and consistently consumed by any client with basic HTTP support. + +To provide the smoothest possible experience for developers, it's important to have these APIs follow consistent design guidelines, thus making using them easy and intuitive. +This document establishes the guidelines to be followed by Microsoft REST API developers for developing such APIs consistently. + +The benefits of consistency accrue in aggregate as well; consistency allows teams to leverage common code, patterns, documentation and design decisions. + +These guidelines aim to achieve the following: +- Define consistent practices and patterns for all API endpoints across Microsoft. +- Adhere as closely as possible to accepted REST/HTTP best practices in the industry at-large. [\*] +- Make accessing Microsoft Services via REST interfaces easy for all application developers. +- Allow service developers to leverage the prior work of other services to implement, test and document REST endpoints defined consistently. +- Allow for partners (e.g., non-Microsoft entities) to use these guidelines for their own REST endpoint design. + +[\*] Note: The guidelines are designed to align with building services which comply with the REST architectural style, though they do not address or require building services that follow the REST constraints. +The term "REST" is used throughout this document to mean services that are in the spirit of REST rather than adhering to REST by the book.* + +### 3.1. Recommended reading +Understanding the philosophy behind the REST Architectural Style is recommended for developing good HTTP-based services. +If you are new to RESTful design, here are some good resources: + +[REST on Wikipedia][rest-on-wikipedia] -- Overview of common definitions and core ideas behind REST. + +[REST Dissertation][fielding] -- The chapter on REST in Roy Fielding's dissertation on Network Architecture, "Architectural Styles and the Design of Network-based Software Architectures" + +[RFC 7231][rfc-7231] -- Defines the specification for HTTP/1.1 semantics, and is considered the authoritative resource. + +[REST in Practice][rest-in-practice] -- Book on the fundamentals of REST. + +## 4. Interpreting the guidelines +### 4.1. Application of the guidelines +These guidelines are applicable to any REST API exposed publicly by Microsoft or any partner service. +Private or internal APIs SHOULD also try to follow these guidelines because internal services tend to eventually be exposed publicly. + Consistency is valuable to not only external customers but also internal service consumers, and these guidelines offer best practices useful for any service. + +There are legitimate reasons for exemption from these guidelines. +Obviously, a REST service that implements or must interoperate with some externally defined REST API must be compatible with that API and not necessarily these guidelines. +Some services MAY also have special performance needs that require a different format, such as a binary protocol. + +### 4.2. Guidelines for existing services and versioning of services +We do not recommend making a breaking change to a service that predates these guidelines simply for the sake of compliance. +The service SHOULD try to become compliant at the next version release when compatibility is being broken anyway. +When a service adds a new API, that API SHOULD be consistent with the other APIs of the same version. +So if a service was written against version 1.0 of the guidelines, new APIs added incrementally to the service SHOULD also follow version 1.0. The service can then upgrade to align with the latest version of the guidelines at the service's next major release. + +### 4.3. Requirements language +The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119][rfc-2119]. + +### 4.4. License + +This work is licensed under the Creative Commons Attribution 4.0 International License. +To view a copy of this license, visit https://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + +## 5. Taxonomy +As part of onboarding to Microsoft REST API Guidelines, services MUST comply with the taxonomy defined below. + +### 5.1. Errors +Errors, or more specifically Service Errors, are defined as a client passing invalid data to the service and the service _correctly_ rejecting that data. +Examples include invalid credentials, incorrect parameters, unknown version IDs, or similar. +These are generally "4xx" HTTP error codes and are the result of a client passing incorrect or invalid data. + +Errors do _not_ contribute to overall API availability. + +### 5.2. Faults +Faults, or more specifically Service Faults, are defined as the service failing to correctly return in response to a valid client request. +These are generally "5xx" HTTP error codes. + +Faults _do_ contribute to the overall API availability. + +Calls that fail due to rate limiting or quota failures MUST NOT count as faults. +Calls that fail as the result of a service fast-failing requests (often for its own protection) do count as faults. + +### 5.3. Latency +Latency is defined as how long a particular API call takes to complete, measured as closely to the client as possible. +This metric applies to both synchronous and asynchronous APIs in the same way. +For long running calls, the latency is measured on the initial request and measures how long that call (not the overall operation) takes to complete. + +### 5.4. Time to complete +Services that expose long operations MUST track "Time to Complete" metrics around those operations. + +### 5.5. Long running API faults +For a Long Running API, it's possible for both the initial request which begins the operation and the request which retrieves the results to technically work (each passing back a 200) but for the underlying operation to have failed. +Long Running faults MUST roll up as faults into the overall Availability metrics. + +## 6. Client guidance +To ensure the best possible experience for clients talking to a REST service, clients SHOULD adhere to the following best practices: + +### 6.1. Ignore rule +For loosely coupled clients where the exact shape of the data is not known before the call, if the server returns something the client wasn't expecting, the client MUST safely ignore it. + +Some services MAY add fields to responses without changing versions numbers. +Services that do so MUST make this clear in their documentation and clients MUST ignore unknown fields. + +### 6.2. Variable order rule +Clients MUST NOT rely on the order in which data appears in JSON service responses. +For example, clients SHOULD be resilient to the reordering of fields within a JSON object. +When supported by the service, clients MAY request that data be returned in a specific order. +For example, services MAY support the use of the _$orderBy_ querystring parameter to specify the order of elements within a JSON array. +Services MAY also explicitly specify the ordering of some elements as part of the service contract. +For example, a service MAY always return a JSON object's "type" information as the first field in an object to simplify response parsing on the client. +Clients MAY rely on ordering behavior explicitly identified by the service. + +### 6.3. Silent fail rule +Clients requesting OPTIONAL server functionality (such as optional headers) MUST be resilient to the server ignoring that particular functionality. + +## 7. Consistency fundamentals +### 7.1. URL structure +Humans SHOULD be able to easily read and construct URLs. + +This facilitates discovery and eases adoption on platforms without a well-supported client library. + +An example of a well-structured URL is: + +``` +https://api.contoso.com/v1.0/people/jdoe@contoso.com/inbox +``` + +An example URL that is not friendly is: + +``` +https://api.contoso.com/EWS/OData/Users('jdoe@microsoft.com')/Folders('AAMkADdiYzI1MjUzLTk4MjQtNDQ1Yy05YjJkLWNlMzMzYmIzNTY0MwAuAAAAAACzMsPHYH6HQoSwfdpDx-2bAQCXhUk6PC1dS7AERFluCgBfAAABo58UAAA=') +``` + +A frequent pattern that comes up is the use of URLs as values. +Services MAY use URLs as values. +For example, the following is acceptable: + +``` +https://api.contoso.com/v1.0/items?url=https://resources.contoso.com/shoes/fancy +``` + +### 7.2. URL length +The HTTP 1.1 message format, defined in RFC 7230, in section [3.1.1][rfc-7230-3-1-1], defines no length limit on the Request Line, which includes the target URL. +From the RFC: + +> HTTP does not place a predefined limit on the length of a + request-line. [...] A server that receives a request-target longer than any URI it wishes to parse MUST respond + with a 414 (URI Too Long) status code. + +Services that can generate URLs longer than 2,083 characters MUST make accommodations for the clients they wish to support. +Here are some sources for determining what target clients support: + + * [https://stackoverflow.com/a/417184](https://stackoverflow.com/a/417184) + * [https://blogs.msdn.microsoft.com/ieinternals/2014/08/13/url-length-limits/](https://blogs.msdn.microsoft.com/ieinternals/2014/08/13/url-length-limits/) + +Also note that some technology stacks have hard and adjustable URL limits, so keep this in mind as you design your services. + +### 7.3. Canonical identifier +In addition to friendly URLs, resources that can be moved or be renamed SHOULD expose a URL that contains a unique stable identifier. +It MAY be necessary to interact with the service to obtain a stable URL from the friendly name for the resource, as in the case of the "/my" shortcut used by some services. + +The stable identifier is not required to be a GUID. + +An example of a URL containing a canonical identifier is: + +``` +https://api.contoso.com/v1.0/people/7011042402/inbox +``` + +### 7.4. Supported methods +Operations MUST use the proper HTTP methods whenever possible, and operation idempotency MUST be respected. +HTTP methods are frequently referred to as the HTTP verbs. +The terms are synonymous in this context, however the HTTP specification uses the term method. + +Below is a list of methods that Microsoft REST services SHOULD support. +Not all resources will support all methods, but all resources using the methods below MUST conform to their usage. + +Method | Description | Is Idempotent +------- | -------------------------------------------------------------------------------------------------------------------------- | ------------- +GET | Return the current value of an object | True +PUT | Replace an object, or create a named object, when applicable | True +DELETE | Delete an object | True +POST | Create a new object based on the data provided, or submit a command | False +HEAD | Return metadata of an object for a GET response. Resources that support the GET method MAY support the HEAD method as well | True +PATCH | Apply a partial update to an object | False +OPTIONS | Get information about a request; see below for details. | True + +Table 1 + +#### 7.4.1. POST +POST operations SHOULD support the Location response header to specify the location of any created resource that was not explicitly named, via the Location header. + +As an example, imagine a service that allows creation of hosted servers, which will be named by the service: + +```http +POST http://api.contoso.com/account1/servers +``` + +The response would be something like: + +```http +201 Created +Location: http://api.contoso.com/account1/servers/server321 +``` + +Where "server321" is the service-allocated server name. + +Services MAY also return the full metadata for the created item in the response. + +#### 7.4.2. PATCH +PATCH has been standardized by IETF as the method to be used for updating an existing object incrementally (see [RFC 5789][rfc-5789]). +Microsoft REST API Guidelines compliant APIs SHOULD support PATCH. + +#### 7.4.3. Creating resources via PATCH (UPSERT semantics) +Services that allow callers to specify key values on create SHOULD support UPSERT semantics, and those that do MUST support creating resources using PATCH. +Because PUT is defined as a complete replacement of the content, it is dangerous for clients to use PUT to modify data. +Clients that do not understand (and hence ignore) properties on a resource are not likely to provide them on a PUT when trying to update a resource, hence such properties could be inadvertently removed. +Services MAY optionally support PUT to update existing resources, but if they do they MUST use replacement semantics (that is, after the PUT, the resource's properties MUST match what was provided in the request, including deleting any server properties that were not provided). + +Under UPSERT semantics, a PATCH call to a nonexistent resource is handled by the server as a "create", and a PATCH call to an existing resource is handled as an "update". To ensure that an update request is not treated as a create or vice versa, the client MAY specify precondition HTTP headers in the request. +The service MUST NOT treat a PATCH request as an insert if it contains an If-Match header and MUST NOT treat a PATCH request as an update if it contains an If-None-Match header with a value of "*". + +If a service does not support UPSERT, then a PATCH call against a resource that does not exist MUST result in an HTTP "409 Conflict" error. + +#### 7.4.4. Options and link headers +OPTIONS allows a client to retrieve information about a resource, at a minimum by returning the Allow header denoting the valid methods for this resource. + +In addition, services SHOULD include a Link header (see [RFC 5988][rfc-5988]) to point to documentation for the resource in question: + +```http +Link: <{help}>; rel="help" +``` + +Where {help} is the URL to a documentation resource. + +For examples on use of OPTIONS, see [preflighting CORS cross-domain calls][cors-preflight]. + +### 7.5. Standard request headers +The table of request headers below SHOULD be used by Microsoft REST API Guidelines services. +Using these headers is not mandated, but if used they MUST be used consistently. + +All header values MUST follow the syntax rules set forth in the specification where the header field is defined. +Many HTTP headers are defined in [RFC7231][rfc-7231], however a complete list of approved headers can be found in the [IANA Header Registry][IANA-headers]." + +Header | Type | Description +--------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Authorization | String | Authorization header for the request +Date | Date | Timestamp of the request, based on the client's clock, in [RFC 5322][rfc-5322-3-3] date and time format. The server SHOULD NOT make any assumptions about the accuracy of the client's clock. This header MAY be included in the request, but MUST be in this format when supplied. Greenwich Mean Time (GMT) MUST be used as the time zone reference for this header when it is provided. For example: `Wed, 24 Aug 2016 18:41:30 GMT`. Note that GMT is exactly equal to UTC (Coordinated Universal Time) for this purpose. +Accept | Content type | The requested content type for the response such as:
    • application/xml
    • text/xml
    • application/json
    • text/javascript (for JSONP)
    Per the HTTP guidelines, this is just a hint and responses MAY have a different content type, such as a blob fetch where a successful response will just be the blob stream as the payload. For services following OData, the preference order specified in OData SHOULD be followed. +Accept-Encoding | Gzip, deflate | REST endpoints SHOULD support GZIP and DEFLATE encoding, when applicable. For very large resources, services MAY ignore and return uncompressed data. +Accept-Language | "en", "es", etc. | Specifies the preferred language for the response. Services are not required to support this, but if a service supports localization it MUST do so through the Accept-Language header. +Accept-Charset | Charset type like "UTF-8" | Default is UTF-8, but services SHOULD be able to handle ISO-8859-1. +Content-Type | Content type | Mime type of request body (PUT/POST/PATCH) +Prefer | return=minimal, return=representation | If the return=minimal preference is specified, services SHOULD return an empty body in response to a successful insert or update. If return=representation is specified, services SHOULD return the created or updated resource in the response. Services SHOULD support this header if they have scenarios where clients would sometimes benefit from responses, but sometimes the response would impose too much of a hit on bandwidth. +If-Match, If-None-Match, If-Range | String | Services that support updates to resources using optimistic concurrency control MUST support the If-Match header to do so. Services MAY also use other headers related to ETags as long as they follow the HTTP specification. + +### 7.6. Standard response headers +Services SHOULD return the following response headers, except where noted in the "required" column. + +Response Header | Required | Description +------------------ | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Date | All responses | Timestamp the response was processed, based on the server's clock, in [RFC 5322][rfc-5322-3-3] date and time format. This header MUST be included in the response. Greenwich Mean Time (GMT) MUST be used as the time zone reference for this header. For example: `Wed, 24 Aug 2016 18:41:30 GMT`. Note that GMT is exactly equal to UTC (Coordinated Universal Time) for this purpose. +Content-Type | All responses | The content type +Content-Encoding | All responses | GZIP or DEFLATE, as appropriate +Preference-Applied | When specified in request | Whether a preference indicated in the Prefer request header was applied +ETag | When the requested resource has an entity tag | The ETag response-header field provides the current value of the entity tag for the requested variant. Used with If-Match, If-None-Match and If-Range to implement optimistic concurrency control. + +### 7.7. Custom headers +Custom headers MUST NOT be required for the basic operation of a given API. + +Some of the guidelines in this document prescribe the use of nonstandard HTTP headers. +In addition, some services MAY need to add extra functionality, which is exposed via HTTP headers. +The following guidelines help maintain consistency across usage of custom headers. + +Headers that are not standard HTTP headers MUST have one of two formats: + +1. A generic format for headers that are registered as "provisional" with IANA ([RFC 3864][rfc-3864]) +2. A scoped format for headers that are too usage-specific for registration + +These two formats are described below. + +### 7.8. Specifying headers as query parameters +Some headers pose challenges for some scenarios such as AJAX clients, especially when making cross-domain calls where adding headers MAY not be supported. +As such, some headers MAY be accepted as Query Parameters in addition to headers, with the same naming as the header: + +Not all headers make sense as query parameters, including most standard HTTP headers. + +The criteria for considering when to accept headers as parameters are: + +1. Any custom headers MUST be also accepted as parameters. +2. Required standard headers MAY be accepted as parameters. +3. Required headers with security sensitivity (e.g., Authorization header) MIGHT NOT be appropriate as parameters; the service owner SHOULD evaluate these on a case-by-case basis. + +The one exception to this rule is the Accept header. +It's common practice to use a scheme with simple names instead of the full functionality described in the HTTP specification for Accept. + +### 7.9. PII parameters +Consistent with their organization's privacy policy, clients SHOULD NOT transmit personally identifiable information (PII) parameters in the URL (as part of path or query string) because this information can be inadvertently exposed via client, network, and server logs and other mechanisms. + +Consequently, a service SHOULD accept PII parameters transmitted as headers. + +However, there are many scenarios where the above recommendations cannot be followed due to client or software limitations. +To address these limitations, services SHOULD also accept these PII parameters as part of the URL consistent with the rest of these guidelines. + +Services that accept PII parameters -- whether in the URL or as headers -- SHOULD be compliant with privacy policy specified by their organization's engineering leadership. +This will typically include recommending that clients prefer headers for transmission and implementations adhere to special precautions to ensure that logs and other service data collection are properly handled. + +### 7.10. Response formats +For organizations to have a successful platform, they must serve data in formats developers are accustomed to using, and in consistent ways that allow developers to handle responses with common code. + +Web-based communication, especially when a mobile or other low-bandwidth client is involved, has moved quickly in the direction of JSON for a variety of reasons, including its tendency to be lighter weight and its ease of consumption with JavaScript-based clients. + +JSON property names SHOULD be camelCased. + +Services SHOULD provide JSON as the default encoding. + +#### 7.10.1. Clients-specified response format +In HTTP, response format SHOULD be requested by the client using the Accept header. +This is a hint, and the server MAY ignore it if it chooses to, even if this isn't typical of well-behaved servers. +Clients MAY send multiple Accept headers and the service MAY choose one of them. + +The default response format (no Accept header provided) SHOULD be application/json, and all services MUST support application/json. + +Accept Header | Response type | Notes +---------------- | ---------------------------------- | ------------------------------------------- +application/json | Payload SHOULD be returned as JSON | Also accept text/javascript for JSONP cases + +```http +GET https://api.contoso.com/v1.0/products/user +Accept: application/json +``` + +#### 7.10.2. Error condition responses +For non-success conditions, developers SHOULD be able to write one piece of code that handles errors consistently across different Microsoft REST API Guidelines services. +This allows building of simple and reliable infrastructure to handle exceptions as a separate flow from successful responses. +The following is based on the OData v4 JSON spec. +However, it is very generic and does not require specific OData constructs. +APIs SHOULD use this format even if they are not using other OData constructs. + +The error response MUST be a single JSON object. +This object MUST have a name/value pair named "error". The value MUST be a JSON object. + +This object MUST contain name/value pairs with the names "code" and "message", and it MAY contain name/value pairs with the names "target", "details" and "innererror." + +The value for the "code" name/value pair is a language-independent string. +Its value is a service-defined error code that SHOULD be human-readable. +This code serves as a more specific indicator of the error than the HTTP error code specified in the response. +Services SHOULD have a relatively small number (about 20) of possible values for "code", and all clients MUST be capable of handling all of them. +Most services will require a much larger number of more specific error codes, which are not interesting to all clients. +These error codes SHOULD be exposed in the "innererror" name/value pair as described below. +Introducing a new value for "code" that is visible to existing clients is a breaking change and requires a version increase. +Services can avoid breaking changes by adding new error codes to "innererror" instead. + +The value for the "message" name/value pair MUST be a human-readable representation of the error. +It is intended as an aid to developers and is not suitable for exposure to end users. +Services wanting to expose a suitable message for end users MUST do so through an [annotation][odata-json-annotations] or custom property. +Services SHOULD NOT localize "message" for the end user, because doing so might make the value unreadable to the app developer who may be logging the value, as well as make the value less searchable on the Internet. + +The value for the "target" name/value pair is the target of the particular error (e.g., the name of the property in error). + +The value for the "details" name/value pair MUST be an array of JSON objects that MUST contain name/value pairs for "code" and "message", and MAY contain a name/value pair for "target", as described above. +The objects in the "details" array usually represent distinct, related errors that occurred during the request. +See example below. + +The value for the "innererror" name/value pair MUST be an object. +The contents of this object are service-defined. +Services wanting to return more specific errors than the root-level code MUST do so by including a name/value pair for "code" and a nested "innererror". Each nested "innererror" object represents a higher level of detail than its parent. +When evaluating errors, clients MUST traverse through all of the nested "innererrors" and choose the deepest one that they understand. +This scheme allows services to introduce new error codes anywhere in the hierarchy without breaking backwards compatibility, so long as old error codes still appear. +The service MAY return different levels of depth and detail to different callers. +For example, in development environments, the deepest "innererror" MAY contain internal information that can help debug the service. +To guard against potential security concerns around information disclosure, services SHOULD take care not to expose too much detail unintentionally. +Error objects MAY also include custom server-defined name/value pairs that MAY be specific to the code. +Error types with custom server-defined properties SHOULD be declared in the service's metadata document. +See example below. + +Error responses MAY contain [annotations][odata-json-annotations] in any of their JSON objects. + +We recommend that for any transient errors that may be retried, services SHOULD include a Retry-After HTTP header indicating the minimum number of seconds that clients SHOULD wait before attempting the operation again. + +##### ErrorResponse : Object + +Property | Type | Required | Description +-------- | ---- | -------- | ----------- +`error` | Error | ✔ | The error object. + +##### Error : Object + +Property | Type | Required | Description +-------- | ---- | -------- | ----------- +`code` | String | ✔ | One of a server-defined set of error codes. +`message` | String | ✔ | A human-readable representation of the error. +`target` | String | | The target of the error. +`details` | Error[] | | An array of details about specific errors that led to this reported error. +`innererror` | InnerError | | An object containing more specific information than the current object about the error. + +##### InnerError : Object + +Property | Type | Required | Description +-------- | ---- | -------- | ----------- +`code` | String | | A more specific error code than was provided by the containing error. +`innererror` | InnerError | | An object containing more specific information than the current object about the error. + +##### Examples + +Example of "innererror": + +```json +{ + "error": { + "code": "BadArgument", + "message": "Previous passwords may not be reused", + "target": "password", + "innererror": { + "code": "PasswordError", + "innererror": { + "code": "PasswordDoesNotMeetPolicy", + "minLength": "6", + "maxLength": "64", + "characterTypes": ["lowerCase","upperCase","number","symbol"], + "minDistinctCharacterTypes": "2", + "innererror": { + "code": "PasswordReuseNotAllowed" + } + } + } + } +} +``` + +In this example, the most basic error code is "BadArgument", but for clients that are interested, there are more specific error codes in "innererror." +The "PasswordReuseNotAllowed" code may have been added by the service at a later date, having previously only returned "PasswordDoesNotMeetPolicy." +Existing clients do not break when the new error code is added, but new clients MAY take advantage of it. +The "PasswordDoesNotMeetPolicy" error also includes additional name/value pairs that allow the client to determine the server's configuration, validate the user's input programmatically, or present the server's constraints to the user within the client's own localized messaging. + +Example of "details": + +```json +{ + "error": { + "code": "BadArgument", + "message": "Multiple errors in ContactInfo data", + "target": "ContactInfo", + "details": [ + { + "code": "NullValue", + "target": "PhoneNumber", + "message": "Phone number must not be null" + }, + { + "code": "NullValue", + "target": "LastName", + "message": "Last name must not be null" + }, + { + "code": "MalformedValue", + "target": "Address", + "message": "Address is not valid" + } + ] + } +} +``` + +In this example there were multiple problems with the request, with each individual error listed in "details." + +### 7.11. HTTP Status Codes +Standard HTTP Status Codes SHOULD be used; see the HTTP Status Code definitions for more information. + +### 7.12. Client library optional +Developers MUST be able to develop on a wide variety of platforms and languages, such as Windows, macOS, Linux, C#, Python, Node.js, and Ruby. + +Services SHOULD be able to be accessed from simple HTTP tools such as curl without significant effort. + +Service developer portals SHOULD provide the equivalent of "Get Developer Token" to facilitate experimentation and curl support. + +## 8. CORS +Services compliant with the Microsoft REST API Guidelines MUST support [CORS (Cross Origin Resource Sharing)][cors]. +Services SHOULD support an allowed origin of CORS * and enforce authorization through valid OAuth tokens. +Services SHOULD NOT support user credentials with origin validation. +There MAY be exceptions for special cases. + +### 8.1. Client guidance +Web developers usually don't need to do anything special to take advantage of CORS. +All of the handshake steps happen invisibly as part of the standard XMLHttpRequest calls they make. + +Many other platforms, such as .NET, have integrated support for CORS. + +#### 8.1.1. Avoiding preflight +Because the CORS protocol can trigger preflight requests that add additional round trips to the server, performance-critical apps might be interested in avoiding them. +The spirit behind CORS is to avoid preflight for any simple cross-domain requests that old non-CORS-capable browsers were able to make. +All other requests require preflight. + +A request is "simple" and avoids preflight if its method is GET, HEAD or POST, and if it doesn't contain any request headers besides Accept, Accept-Language and Content-Language. +For POST requests, the Content-Type header is also allowed, but only if its value is "application/x-www-form-urlencoded", "multipart/form-data" or "text/plain." +For any other headers or values, a preflight request will happen. + +### 8.2. Service guidance + At minimum, services MUST: +- Understand the Origin request header that browsers send on cross-domain requests, and the Access-Control-Request-Method request header that they send on preflight OPTIONS requests that check for access. +- If the Origin header is present in a request: + - If the request uses the OPTIONS method and contains the Access-Control-Request-Method header, then it is a preflight request intended to probe for access before the actual request. Otherwise, it is an actual request. For preflight requests, beyond performing the steps below to add headers, services MUST perform no additional processing and MUST return a 200 OK. For non-preflight requests, the headers below are added in addition to the request's regular processing. + - Add an Access-Control-Allow-Origin header to the response, containing the same value as the Origin request header. Note that this requires services to dynamically generate the header value. Resources that do not require cookies or any other form of [user credentials][cors-user-credentials] MAY respond with a wildcard asterisk (*) instead. Note that the wildcard is acceptable here only, and not for any of the other headers described below. + - If the caller requires access to a response header that is not in the set of [simple response headers][cors-simple-headers] (Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma), then add an Access-Control-Expose-Headers header containing the list of additional response header names the client should have access to. + - If the request requires cookies, then add an Access-Control-Allow-Credentials header set to "true." + - If the request was a preflight request (see first bullet), then the service MUST: + - Add an Access-Control-Allow-Headers response header containing the list of request header names the client is permitted to use. This list need only contain headers that are not in the set of [simple request headers][cors-simple-headers] (Accept, Accept-Language, Content-Language). If there are no restrictions on headers the service accepts, the service MAY simply return the same value as the Access-Control-Request-Headers header sent by the client. + - Add an Access-Control-Allow-Methods response header containing the list of HTTP methods the caller is permitted to use. + +Add an Access-Control-Max-Age pref response header containing the number of seconds for which this preflight response is valid (and hence can be avoided before subsequent actual requests). Note that while it is customary to use a large value like 2592000 (30 days), many browsers self-impose a much lower limit (e.g., five minutes). + +Because browser preflight response caches are notoriously weak, the additional round trip from a preflight response hurts performance. +Services used by interactive Web clients where performance is critical SHOULD avoid patterns that cause a preflight request +- For GET and HEAD calls, avoid requiring request headers that are not part of the simple set above. Allow them to be provided as query parameters instead. + - The Authorization header is not part of the simple set, so the authentication token MUST be sent through the "access_token" query parameter instead, for resources requiring authentication. Note that passing authentication tokens in the URL is not recommended, because it can lead to the token getting recorded in server logs and exposed to anyone with access to those logs. Services that accept authentication tokens through the URL MUST take steps to mitigate the security risks, such as using short-lived authentication tokens, suppressing the auth token from getting logged, and controlling access to server logs. + +- Avoid requiring cookies. XmlHttpRequest will only send cookies on cross-domain requests if the "withCredentials" attribute is set; this also causes a preflight request. + - Services that require cookie-based authentication MUST use a "dynamic canary" to secure all APIs that accept cookies. + +- For POST calls, prefer simple Content-Types in the set of ("application/x-www-form-urlencoded", "multipart/form-data", "text/plain") where applicable. Any other Content-Type will induce a preflight request. + - Services MUST NOT contravene other API recommendations in the name of avoiding CORS preflight requests. In particular, in accordance with recommendations, most POST requests will actually require a preflight request due to the Content-Type. + - If eliminating preflight is critical, then a service MAY support alternative mechanisms for data transfer, but the RECOMMENDED approach MUST also be supported. + +In addition, when appropriate services MAY support the JSONP pattern for simple, GET-only cross-domain access. +In JSONP, services take a parameter indicating the format (_$format=json_) and a parameter indicating a callback (_$callback=someFunc_), and return a text/javascript document containing the JSON response wrapped in a function call with the indicated name. +More on JSONP at Wikipedia: [JSONP](https://en.wikipedia.org/wiki/JSONP). + +## 9. Collections +### 9.1. Item keys +Services MAY support durable identifiers for each item in the collection, and that identifier SHOULD be represented in JSON as "id". These durable identifiers are often used as item keys. + +Collections that support durable identifiers MAY support delta queries. + +### 9.2. Serialization +Collections are represented in JSON using standard array notation. + +### 9.3. Collection URL patterns +Collections are located directly under the service root when they are top level, or as a segment under another resource when scoped to that resource. + +For example: + +```http +GET https://api.contoso.com/v1.0/people +``` + +Whenever possible, services MUST support the "/" pattern. +For example: + +```http +GET https://{serviceRoot}/{collection}/{id} +``` + +Where: +- {serviceRoot} – the combination of host (site URL) + the root path to the service +- {collection} – the name of the collection, unabbreviated, pluralized +- {id} – the value of the unique id property. When using the "/" pattern this MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. + +#### 9.3.1. Nested collections and properties +Collection items MAY contain other collections. +For example, a user collection MAY contain user resources that have multiple addresses: + +```http +GET https://api.contoso.com/v1.0/people/123/addresses +``` + +```json +{ + "value": [ + { "street": "1st Avenue", "city": "Seattle" }, + { "street": "124th Ave NE", "city": "Redmond" } + ] +} +``` + +### 9.4. Big collections +As data grows, so do collections. +Planning for pagination is important for all services. +Therefore, when multiple pages are available, the serialization payload MUST contain the opaque URL for the next page as appropriate. +Refer to the paging guidance for more details. + +Clients MUST be resilient to collection data being either paged or nonpaged for any given request. + +```json +{ + "value":[ + { "id": "Item 1","price": 99.95,"sizes": null}, + { … }, + { … }, + { "id": "Item 99","price": 59.99,"sizes": null} + ], + "@nextLink": "{opaqueUrl}" +} +``` + +### 9.5. Changing collections +POST requests are not idempotent. +This means that two POST requests sent to a collection resource with exactly the same payload MAY lead to multiple items being created in that collection. +This is often the case for insert operations on items with a server-side generated id. + +For example, the following request: + +```http +POST https://api.contoso.com/v1.0/people +``` + +Would lead to a response indicating the location of the new collection item: + +```http +201 Created +Location: https://api.contoso.com/v1.0/people/123 +``` + +And once executed again, would likely lead to another resource: + +```http +201 Created +Location: https://api.contoso.com/v1.0/people/124 +``` + +While a PUT request would require the indication of the collection item with the corresponding key instead: + +```http +PUT https://api.contoso.com/v1.0/people/123 +``` + +### 9.6. Sorting collections +The results of a collection query MAY be sorted based on property values. +The property is determined by the value of the _$orderBy_ query parameter. + +The value of the _$orderBy_ parameter contains a comma-separated list of expressions used to sort the items. +A special case of such an expression is a property path terminating on a primitive property. + +The expression MAY include the suffix "asc" for ascending or "desc" for descending, separated from the property name by one or more spaces. +If "asc" or "desc" is not specified, the service MUST order by the specified property in ascending order. + +NULL values MUST sort as "less than" non-NULL values. + +Items MUST be sorted by the result values of the first expression, and then items with the same value for the first expression are sorted by the result value of the second expression, and so on. +The sort order is the inherent order for the type of the property. + +For example: + +```http +GET https://api.contoso.com/v1.0/people?$orderBy=name +``` + +Will return all people sorted by name in ascending order. + +For example: + +```http +GET https://api.contoso.com/v1.0/people?$orderBy=name desc +``` + +Will return all people sorted by name in descending order. + +Sub-sorts can be specified by a comma-separated list of property names with OPTIONAL direction qualifier. + +For example: + +```http +GET https://api.contoso.com/v1.0/people?$orderBy=name desc,hireDate +``` + +Will return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. + +Sorting MUST compose with filtering such that: + +```http +GET https://api.contoso.com/v1.0/people?$filter=name eq 'david'&$orderBy=hireDate +``` + +Will return all people whose name is David sorted in ascending order by hireDate. + +#### 9.6.1. Interpreting a sorting expression +Sorting parameters MUST be consistent across pages, as both client and server-side paging is fully compatible with sorting. + +If a service does not support sorting by a property named in a _$orderBy_ expression, the service MUST respond with an error message as defined in the Responding to Unsupported Requests section. + +### 9.7. Filtering +The _$filter_ querystring parameter allows clients to filter a collection of resources that are addressed by a request URL. +The expression specified with _$filter_ is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. +Resources for which the expression evaluates to false or to null, or which reference properties that are unavailable due to permissions, are omitted from the response. + +Example: return all Products whose Price is less than $10.00 + +```http +GET https://api.contoso.com/v1.0/products?$filter=price lt 10.00 +``` + +The value of the _$filter_ option is a Boolean expression. + +#### 9.7.1. Filter operations +Services that support _$filter_ SHOULD support the following minimal set of operations. + +Operator | Description | Example +-------------------- | --------------------- | ----------------------------------------------------- +Comparison Operators | | +eq | Equal | city eq 'Redmond' +ne | Not equal | city ne 'London' +gt | Greater than | price gt 20 +ge | Greater than or equal | price ge 10 +lt | Less than | price lt 20 +le | Less than or equal | price le 100 +Logical Operators | | +and | Logical and | price le 200 and price gt 3.5 +or | Logical or | price le 3.5 or price gt 200 +not | Logical negation | not price le 3.5 +Grouping Operators | | +( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 + +#### 9.7.2. Operator examples +The following examples illustrate the use and semantics of each of the logical operators. + +Example: all products with a name equal to 'Milk' + +```http +GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' +``` + +Example: all products with a name not equal to 'Milk' + +```http +GET https://api.contoso.com/v1.0/products?$filter=name ne 'Milk' +``` + +Example: all products with the name 'Milk' that also have a price less than 2.55: + +```http +GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' and price lt 2.55 +``` + +Example: all products that either have the name 'Milk' or have a price less than 2.55: + +```http +GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' or price lt 2.55 +``` + +Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: + +```http +GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 +``` + +#### 9.7.3. Operator precedence +Services MUST use the following operator precedence for supported operators when evaluating _$filter_ expressions. +Operators are listed by category in order of precedence from highest to lowest. +Operators in the same category have equal precedence: + +| Group | Operator | Description | +|:----------------|:---------|:----------------------| +| Grouping | ( ) | Precedence grouping | +| Unary | not | Logical Negation | +| Relational | gt | Greater Than | +| | ge | Greater than or Equal | +| | lt | Less Than | +| | le | Less than or Equal | +| Equality | eq | Equal | +| | ne | Not Equal | +| Conditional AND | and | Logical And | +| Conditional OR | or | Logical Or | + +### 9.8. Pagination +RESTful APIs that return collections MAY return partial sets. +Consumers of these services MUST expect partial result sets and correctly page through to retrieve an entire set. + +There are two forms of pagination that MAY be supported by RESTful APIs. +Server-driven paging mitigates against denial-of-service attacks by forcibly paginating a request over multiple response payloads. +Client-driven paging enables clients to request only the number of resources that it can use at a given time. + +Sorting and Filtering parameters MUST be consistent across pages, because both client- and server-side paging is fully compatible with both filtering and sorting. + +#### 9.8.1. Server-driven paging +Paginated responses MUST indicate a partial result by including a continuation token in the response. +The absence of a continuation token means that no additional pages are available. + +Clients MUST treat the continuation URL as opaque, which means that query options may not be changed while iterating over a set of partial results. + +Example: + +```http +GET http://api.contoso.com/v1.0/people HTTP/1.1 +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + ..., + "value": [...], + "@nextLink": "{opaqueUrl}" +} +``` + +#### 9.8.2. Client-driven paging +Clients MAY use _$top_ and _$skip_ query parameters to specify a number of results to return and an offset into the collection. + +The server SHOULD honor the values specified by the client; however, clients MUST be prepared to handle responses that contain a different page size or contain a continuation token. + +When both _$top_ and _$skip_ are given by a client, the server SHOULD first apply _$skip_ and then _$top_ on the collection. + +Note: If the server can't honor _$top_ and/or _$skip_, the server MUST return an error to the client informing about it instead of just ignoring the query options. +This will avoid the risk of the client making assumptions about the data returned. + +Example: + +```http +GET http://api.contoso.com/v1.0/people?$top=5&$skip=2 HTTP/1.1 +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + ..., + "value": [...] +} +``` + +#### 9.8.3. Additional considerations +**Stable order prerequisite:** Both forms of paging depend on the collection of items having a stable order. +The server MUST supplement any specified order criteria with additional sorts (typically by key) to ensure that items are always ordered consistently. + +**Missing/repeated results:** Even if the server enforces a consistent sort order, results MAY be missing or repeated based on creation or deletion of other resources. +Clients MUST be prepared to deal with these discrepancies. +The server SHOULD always encode the record ID of the last read record, helping the client in the process of managing repeated/missing results. + +**Combining client- and server-driven paging:** Note that client-driven paging does not preclude server-driven paging. +If the page size requested by the client is larger than the default page size supported by the server, the expected response would be the number of results specified by the client, paginated as specified by the server paging settings. + +**Page Size:** Clients MAY request server-driven paging with a specific page size by specifying a _$maxpagesize_ preference. +The server SHOULD honor this preference if the specified page size is smaller than the server's default page size. + +**Paginating embedded collections:** It is possible for both client-driven paging and server-driven paging to be applied to embedded collections. +If a server paginates an embedded collection, it MUST include additional continuation tokens as appropriate. + +**Recordset count:** Developers who want to know the full number of records across all pages, MAY include the query parameter _$count=true_ to tell the server to include the count of items in the response. + +### 9.9. Compound collection operations +Filtering, Sorting and Pagination operations MAY all be performed against a given collection. +When these operations are performed together, the evaluation order MUST be: + +1. **Filtering**. This includes all range expressions performed as an AND operation. +2. **Sorting**. The potentially filtered list is sorted according to the sort criteria. +3. **Pagination**. The materialized paginated view is presented over the filtered, sorted list. This applies to both server-driven pagination and client-driven pagination. + +### 9.10. Empty Results +When a filter is performed on a collection and the result set is empty you MUST respond with a valid response body and a 200 response code. +In this example the filters supplied by the client resulted in a empty result set. +The response body is returned as normal and the _value_ attribute is set to a empty collection. +A client MAY be expecting metadata attributes like _maxItems_ based on the format of your responses to similar calls which produced results. +You SHOULD maintain consistency in your API whenever possible. + +```http +GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + ..., + "maxItems": 0, + "value": [] +} +``` + +## 10. Delta queries +Services MAY choose to support delta queries. + +### 10.1. Delta links +Delta links are opaque, service-generated links that the client uses to retrieve subsequent changes to a result. + +At a conceptual level delta links are based on a defining query that describes the set of results for which changes are being tracked. +The delta link encodes the collection of entities for which changes are being tracked, along with a starting point from which to track changes. + +If the query contains a filter, the response MUST include only changes to entities matching the specified criteria. +The key principles of the Delta Query are: +- Every item in the set MUST have a persistent identifier. That identifier SHOULD be represented as "id". This identifier is a service defined opaque string that MAY be used by the client to track object across calls. +- The delta MUST contain an entry for each entity that newly matches the specified criteria, and MUST contain a "@removed" entry for each entity that no longer matches the criteria. +- Re-evaluate the query and compare it to original set of results; every entry uniquely in the current set MUST be returned as an Add operation, and every entry uniquely in the original set MUST be returned as a "remove" operation. +- Each entity that previously did not match the criteria but matches it now MUST be returned as an "add"; conversely, each entity that previously matched the query but no longer does MUST be returned as a "@removed" entry. +- Entities that have changed MUST be included in the set using their standard representation. +- Services MAY add additional metadata to the "@removed" node, such as a reason for removal, or a "removed at" timestamp. We recommend teams coordinate with the Microsoft REST API Guidelines Working Group on extensions to help maintain consistency. + +The delta link MUST NOT encode any client top or skip value. + +### 10.2. Entity representation +Added and updated entities are represented in the entity set using their standard representation. +From the perspective of the set, there is no difference between an added or updated entity. + +Removed entities are represented using only their "id" and an "@removed" node. +The presence of an "@removed" node MUST represent the removal of the entry from the set. + +### 10.3. Obtaining a delta link +A delta link is obtained by querying a collection or entity and appending a $delta query string parameter. +For example: + +```http +GET https://api.contoso.com/v1.0/people?$delta +HTTP/1.1 +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "value":[ + { "id": "1", "name": "Matt"}, + { "id": "2", "name": "Mark"}, + { "id": "3", "name": "John"} + ], + "@deltaLink": "{opaqueUrl}" +} +``` + +Note: If the collection is paginated the deltaLink will only be present on the final page but MUST reflect any changes to the data returned across all pages. + +### 10.4. Contents of a delta link response +Added/Updated entries MUST appear as regular JSON objects, with regular item properties. +Returning the added/modified items in their regular representation allows the client to merge them into their existing "cache" using standard merge concepts based on the "id" field. + +Entries removed from the defined collection MUST be included in the response. +Items removed from the set MUST be represented using only their "id" and an "@removed" node. + +### 10.5. Using a delta link +The client requests changes by invoking the GET method on the delta link. +The client MUST use the delta URL as is -- in other words the client MUST NOT modify the URL in any way (e.g., parsing it and adding additional query string parameters). +In this example: + +```http +GET https://{opaqueUrl} HTTP/1.1 +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "value":[ + { "id": "1", "name": "Mat"}, + { "id": "2", "name": "Marc"}, + { "id": "3", "@removed": {} }, + { "id": "4", "name": "Luc"} + ], + "@deltaLink": "{opaqueUrl}" +} +``` + +The results of a request against the delta link may span multiple pages but MUST be ordered by the service across all pages in such a way as to ensure a deterministic result when applied in order to the response that contained the delta link. + +If no changes have occurred, the response is an empty collection that contains a delta link for subsequent changes if requested. +This delta link MAY be identical to the delta link resulting in the empty collection of changes. + +If the delta link is no longer valid, the service MUST respond with _410 Gone_. The response SHOULD include a Location header that the client can use to retrieve a new baseline set of results. + +## 11. JSON standardizations +### 11.1. JSON formatting standardization for primitive types +Primitive values MUST be serialized to JSON following the rules of [RFC8259][rfc-8259]. + +**Important note for 64bit integers:** JavaScript will silently truncate integers larger than `Number.MAX_SAFE_INTEGER` (2^53-1) or numbers smaller than `Number.MIN_SAFE_INTEGER` (-2^53+1). If the service is expected to return integer values outside the range of safe values, strongly consider returning the value as a string in order to maximize interoperability and avoid data loss. + +### 11.2. Guidelines for dates and times +#### 11.2.1. Producing dates +Services MUST produce dates using the `DateLiteral` format, and SHOULD use the `Iso8601Literal` format unless there are compelling reasons to do otherwise. +Services that do use the `StructuredDateLiteral` format MUST NOT produce dates using the `T` kind unless BOTH the additional precision is REQUIRED, and ECMAScript clients are explicitly unsupported. +(Non-Normative statement: When deciding which particular `DateKind` to standardize on, the approximate order of preference is `E, C, U, W, O, X, I, T`. +This optimizes for ECMAScript, .NET, and C++ programmers, in that order.) + +#### 11.2.2. Consuming dates +Services MUST accept dates from clients that use the same `DateLiteral` format (including the `DateKind`, if applicable) that they produce, and SHOULD accept dates using any `DateLiteral` format. + +#### 11.2.3. Compatibility +Services MUST use the same `DateLiteral` format (including the same `DateKind`, if applicable) for all resources of the same type, and SHOULD use the same `DateLiteral` format (and `DateKind`, if applicable) for all resources across the entire service. + +Any change to the `DateLiteral` format produced by the service (including the `DateKind`, if applicable) and any reductions in the `DateLiteral` formats (and `DateKind`, if applicable) accepted by the service MUST be treated as a breaking change. +Any widening of the `DateLiteral` formats accepted by the service is NOT considered a breaking change. + +### 11.3. JSON serialization of dates and times +Round-tripping serialized dates with JSON is a hard problem. +Although ECMAScript supports literals for most built-in types, it does not define a literal format for dates. +The Web has coalesced around the [ECMAScript subset of ISO 8601 date formats (ISO 8601)][iso-8601], but there are situations where this format is not desirable. +For those cases, this document defines a JSON serialization format that can be used to unambiguously represent dates in different formats. +Other serialization formats (such as XML) could be derived from this format. + +#### 11.3.1. The `DateLiteral` format +Dates represented in JSON are serialized using the following grammar. +Informally, a `DateValue` is either an ISO 8601-formatted string or a JSON object containing two properties named `kind` and `value` that together define a point in time. +The following is not a context-free grammar; in particular, the interpretation of `DateValue` depends on the value of `DateKind`, but this minimizes the number of productions required to describe the format. + +``` +DateLiteral: + Iso8601Literal + StructuredDateLiteral + +Iso8601Literal: + A string literal as defined in https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15. Note that the full grammar for ISO 8601 (such as "basic format" without separators) is not supported. + All dates default to UTC unless specified otherwise. + +StructuredDateLiteral: + { DateKindProperty , DateValueProperty } + { DateValueProperty , DateKindProperty } + +DateKindProperty + "kind" : DateKind + +DateKind: + "C" ; see below + "E" ; see below + "I" ; see below + "O" ; see below + "T" ; see below + "U" ; see below + "W" ; see below + "X" ; see below + +DateValueProperty: + "value" : DateValue + +DateValue: + UnsignedInteger ; not defined here + SignedInteger ; not defined here + RealNumber ; not defined here + Iso8601Literal ; as above +``` + +#### 11.3.2. Commentary on date formatting +A `DateLiteral` using the `Iso8601Literal` production is relatively straightforward. +Here is an example of an object with a property named `creationDate` that is set to February 13, 2015, at 1:15 p.m. UTC: + +```json +{ "creationDate" : "2015-02-13T13:15Z" } +``` + +The `StructuredDateLiteral` consists of a `DateKind` and an accompanying `DateValue` whose valid values (and their interpretation) depend on the `DateKind`. The following table describes the valid combinations and their meaning: + +DateKind | DateValue | Colloquial Name & Interpretation | More Info +-------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- +C | UnsignedInteger | "CLR"; number of milliseconds since midnight January 1, 0001; negative values are not allowed. *See note below.* | [MSDN][clr-time] +E | SignedInteger | "ECMAScript"; number of milliseconds since midnight, January 1, 1970. | [ECMA International][ecmascript-time] +I | Iso8601Literal | "ISO 8601"; a string limited to the ECMAScript subset. | +O | RealNumber | "OLE Date"; integral part is the number of days since midnight, December 31, 1899, and fractional part is the time within the day (0.5 = midday). | [MSDN][ole-date] +T | SignedInteger | "Ticks"; number of ticks (100-nanosecond intervals) since midnight January 1, 1601. *See note below.* | [MSDN][ticks-time] +U | SignedInteger | "UNIX"; number of seconds since midnight, January 1, 1970. | [MSDN][unix-time] +W | SignedInteger | "Windows"; number of milliseconds since midnight January 1, 1601. *See note below.* | [MSDN][windows-time] +X | RealNumber | "Excel"; as for `O` but the year 1900 is incorrectly treated as a leap year, and day 0 is "January 0 (zero)". | [Microsoft Support][excel-time] + +**Important note for `C` and `W` kinds:** The native CLR and Windows times are represented by 100-nanosecond "tick" values. +To interoperate with ECMAScript clients that have limited precision, _these values MUST be converted to and from milliseconds_ when (de)serialized as a `DateLiteral`. +One millisecond is equivalent to 10,000 ticks. + +**Important note for `T` kind:** This kind preserves the full fidelity of the Windows native time formats (and is trivially convertible to and from the native CLR format) but is incompatible with ECMAScript clients. +Therefore, its use SHOULD be limited to only those scenarios that both require the additional precision and do not need to interoperate with ECMAScript clients. + +Here is the same example of an object with a property named creationDate that is set to February 13, 2015, at 1:15 p.m. UTC, using several formats: + +```json +[ + { "creationDate" : { "kind" : "O", "value" : 42048.55 } }, + { "creationDate" : { "kind" : "E", "value" : 1423862100000 } } +] +``` + +One of the benefits of separating the kind from the value is that once a client knows the kind used by a particular service, it can interpret the value without requiring any additional parsing. +In the common case of the value being a number, this makes coding easier for developers: + +```csharp +// We know this service always gives out ECMAScript-format dates +var date = new Date(serverResponse.someObject.creationDate.value); +``` + +### 11.4. Durations +[Durations][wikipedia-iso8601-durations] need to be serialized in conformance with [ISO 8601][wikipedia-iso8601-durations]. +Durations are "represented by the format `P[n]Y[n]M[n]DT[n]H[n]M[n]S`." +From the standard: +- P is the duration designator (historically called "period") placed at the start of the duration representation. +- Y is the year designator that follows the value for the number of years. +- M is the month designator that follows the value for the number of months. +- W is the week designator that follows the value for the number of weeks. +- D is the day designator that follows the value for the number of days. +- T is the time designator that precedes the time components of the representation. +- H is the hour designator that follows the value for the number of hours. +- M is the minute designator that follows the value for the number of minutes. +- S is the second designator that follows the value for the number of seconds. + +For example, "P3Y6M4DT12H30M5S" represents a duration of "three years, six months, four days, twelve hours, thirty minutes, and five seconds." + +### 11.5. Intervals +[Intervals][wikipedia-iso8601-intervals] are defined as part of [ISO 8601][wikipedia-iso8601-intervals]. +- Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z" +- Start and duration, such as "2007-03-01T13:00:00Z/P1Y2M10DT2H30M" +- Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z" +- Duration only, such as "P1Y2M10DT2H30M", with additional context information + +### 11.6. Repeating intervals +[Repeating Intervals][wikipedia-iso8601-repeatingintervals], as per [ISO 8601][wikipedia-iso8601-repeatingintervals], are: + +> Formed by adding "R[n]/" to the beginning of an interval expression, where R is used as the letter itself and [n] is replaced by the number of repetitions. +Leaving out the value for [n] means an unbounded number of repetitions. + +For example, to repeat the interval of "P1Y2M10DT2H30M" five times starting at "2008-03-01T13:00:00Z", use "R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M." + +## 12. Versioning +**All APIs compliant with the Microsoft REST API Guidelines MUST support explicit versioning.** It's critical that clients can count on services to be stable over time, and it's critical that services can add features and make changes. + +### 12.1. Versioning formats +Services are versioned using a Major.Minor versioning scheme. +Services MAY opt for a "Major" only version scheme in which case the ".0" is implied and all other rules in this section apply. +Two options for specifying the version of a REST API request are supported: +- Embedded in the path of the request URL, at the end of the service root: `https://api.contoso.com/v1.0/products/users` +- As a query string parameter of the URL: `https://api.contoso.com/products/users?api-version=1.0` + +Guidance for choosing between the two options is as follows: + +1. Services co-located behind a DNS endpoint MUST use the same versioning mechanism. +2. In this scenario, a consistent user experience across the endpoint is paramount. The Microsoft REST API Guidelines Working Group recommends that new top-level DNS endpoints are not created without explicit conversations with your organization's leadership team. +3. Services that guarantee the stability of their REST API's URL paths, even through future versions of the API, MAY adopt the query string parameter mechanism. This means the naming and structure of the relationships described in the API cannot evolve after the API ships, even across versions with breaking changes. +4. Services that cannot ensure URL path stability across future versions MUST embed the version in the URL path. + +Certain bedrock services such as Microsoft's Azure Active Directory may be exposed behind multiple endpoints. +Such services MUST support the versioning mechanisms of each endpoint, even if that means supporting multiple versioning mechanisms. + +#### 12.1.1. Group versioning +Group versioning is an OPTIONAL feature that MAY be offered on services using the query string parameter mechanism. +Group versions allow for logical grouping of API endpoints under a common versioning moniker. +This allows developers to look up a single version number and use it across multiple endpoints. +Group version numbers are well known, and services SHOULD reject any unrecognized values. + +Internally, services will take a Group Version and map it to the appropriate Major.Minor version. + +The Group Version format is defined as YYYY-MM-DD, for example 2012-12-07 for December 7, 2012. This Date versioning format applies only to Group Versions and SHOULD NOT be used as an alternative to Major.Minor versioning. + +##### Examples of group versioning + +| Group | Major.Minor | +|:-----------|:------------| +| 2012-12-01 | 1.0 | +| | 1.1 | +| | 1.2 | +| 2013-03-21 | 1.0 | +| | 2.0 | +| | 3.0 | +| | 3.1 | +| | 3.2 | +| | 3.3 | + +Version Format | Example | Interpretation +----------------------------- | ---------------------- | ------------------------------------------ +{groupVersion} | 2013-03-21, 2012-12-01 | 3.3, 1.2 +{majorVersion} | 3 | 3.0 +{majorVersion}.{minorVersion} | 1.2 | 1.2 + +Clients can specify either the group version or the Major.Minor version: + +For example: + +```http +GET http://api.contoso.com/acct1/c1/blob2?api-version=1.0 +``` + +```http +PUT http://api.contoso.com/acct1/c1/b2?api-version=2011-12-07 +``` + +### 12.2. When to version +Services MUST increment their version number in response to any breaking API change. +See the following section for a detailed discussion of what constitutes a breaking change. +Services MAY increment their version number for nonbreaking changes as well, if desired. + +Use a new major version number to signal that support for existing clients will be deprecated in the future. +When introducing a new major version, services MUST provide a clear upgrade path for existing clients and develop a plan for deprecation that is consistent with their business group's policies. +Services SHOULD use a new minor version number for all other changes. + +Online documentation of versioned services MUST indicate the current support status of each previous API version and provide a path to the latest version. + +### 12.3. Definition of a breaking change +Changes to the contract of an API are considered a breaking change. +Changes that impact the backwards compatibility of an API are a breaking change. + +Teams MAY define backwards compatibility as their business needs require. +For example, Azure defines the addition of a new JSON field in a response to be not backwards compatible. +Office 365 has a looser definition of backwards compatibility and allows JSON fields to be added to responses. + +Clear examples of breaking changes: + +1. Removing or renaming APIs or API parameters +2. Changes in behavior for an existing API +3. Changes in Error Codes and Fault Contracts +4. Anything that would violate the [Principle of Least Astonishment][principle-of-least-astonishment] + +Services MUST explicitly define their definition of a breaking change, especially with regard to adding new fields to JSON responses and adding new API arguments with default fields. +Services that are co-located behind a DNS Endpoint with other services MUST be consistent in defining contract extensibility. + +The applicable changes described [in this section of the OData V4 spec][odata-breaking-changes] SHOULD be considered part of the minimum bar that all services MUST consider a breaking change. + +## 13. Long running operations +Long running operations, sometimes called async operations, tend to mean different things to different people. +This section sets forth guidance around different types of long running operations, and describes the wire protocols and best practices for these types of operations. + +1. One or more clients MUST be able to monitor and operate on the same resource at the same time. +2. The state of the system SHOULD be discoverable and testable at all times. Clients SHOULD be able to determine the system state even if the operation tracking resource is no longer active. The act of querying the state of a long running operation should itself leverage principles of the web. i.e. well-defined resources with uniform interface semantics. Clients MAY issue a GET on some resource to determine the state of a long running operation +3. Long running operations SHOULD work for clients looking to "Fire and Forget" and for clients looking to actively monitor and act upon results. +4. Cancellation does not explicitly mean rollback. On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. Following a cancelled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. + +### 13.1. Resource based long running operations (RELO) +Resource based modeling is where the status of an operation is encoded in the resource and the wire protocol used is the standard synchronous protocol. +In this model state transitions are well defined and goal states are similarly defined. + +_This is the preferred model for long running operations and should be used wherever possible._ Avoiding the complexity and mechanics of the LRO Wire Protocol makes things simpler for our users and tooling chain. + +An example may be a machine reboot, where the operation itself completes synchronously but the GET operation on the virtual machine resource would have a "state: Rebooting", "state: Running" that could be queried at any time. + +This model MAY integrate Push Notifications. + +While most operations are likely to be POST semantics, in addition to POST semantics, services MAY support PUT semantics via routing to simplify their APIs. +For example, a user that wants to create a database named "db1" could call: + +```http +PUT https://api.contoso.com/v1.0/databases/db1 +``` + +In this scenario the databases segment is processing the PUT operation. + +Services MAY also use the hybrid defined below. + +### 13.2. Stepwise long running operations +A stepwise operation is one that takes a long, and often unpredictable, length of time to complete, and doesn't offer state transition modeled in the resource. +This section outlines the approach that services should use to expose such long running operations. + +Service MAY expose stepwise operations. + +> Stepwise Long Running Operations are sometimes called "Async" operations. +This causes confusion, as it mixes elements of platforms ("Async / await", "promises", "futures") with elements of API operation. +This document uses the term "Stepwise Long Running Operation" or often just "Stepwise Operation" to avoid confusion over the word "Async". + +Services MUST perform as much synchronous validation as practical on stepwise requests. +Services MUST prioritize returning errors in a synchronous way, with the goal of having only "Valid" operations processed using the long running operation wire protocol. + +For an API that's defined as a Stepwise Long Running Operation the service MUST go through the Stepwise Long Running Operation flow even if the operation can be completed immediately. +In other words, APIs must adopt and stick with an LRO pattern and not change patterns based on circumstance. + +#### 13.2.1. PUT +Services MAY enable PUT requests for entity creation. + +```http +PUT https://api.contoso.com/v1.0/databases/db1 +``` + +In this scenario the _databases_ segment is processing the PUT operation. + +```http +HTTP/1.1 202 Accepted +Operation-Location: https://api.contoso.com/v1.0/operations/123 +``` + +For services that need to return a 201 Created here, use the hybrid flow described below. + +The 202 Accepted should return no body. +The 201 Created case should return the body of the target resource. + +#### 13.2.2. POST +Services MAY enable POST requests for entity creation. + +```http +POST https://api.contoso.com/v1.0/databases/ + +{ + "fileName": "someFile.db", + "color": "red" +} +``` + +```http +HTTP/1.1 202 Accepted +Operation-Location: https://api.contoso.com/v1.0/operations/123 +``` + +#### 13.2.3. POST, hybrid model +Services MAY respond synchronously to POST requests to collections that create a resource even if the resources aren't fully created when the response is generated. +In order to use this pattern, the response MUST include a representation of the incomplete resource and an indication that it is incomplete. + +For example: + +```http +POST https://api.contoso.com/v1.0/databases/ HTTP/1.1 +Host: api.contoso.com +Content-Type: application/json +Accept: application/json + +{ + "fileName": "someFile.db", + "color": "red" +} +``` + +Service response says the database has been created, but indicates the request is not completed by including the Operation-Location header. +In this case the status property in the response payload also indicates the operation has not fully completed. + +```http +HTTP/1.1 201 Created +Location: https://api.contoso.com/v1.0/databases/db1 +Operation-Location: https://api.contoso.com/v1.0/operations/123 + +{ + "databaseName": "db1", + "color": "red", + "Status": "Provisioning", + [ … other fields for "database" …] +} +``` + +#### 13.2.4. Operations resource +Services MAY provide a "/operations" resource at the tenant level. + +Services that provide the "/operations" resource MUST provide GET semantics. +GET MUST enumerate the set of operations, following standard pagination, sorting, and filtering semantics. +The default sort order for this operation MUST be: + +Primary Sort | Secondary Sort +---------------------- | ----------------------- +Not Started Operations | Operation Creation Time +Running Operations | Operation Creation Time +Completed Operations | Operation Creation Time + +Note that "Completed Operations" is a goal state (see below), and may actually be any of several different states such as "successful", "cancelled", "failed" and so forth. + +#### 13.2.5. Operation resource +An operation is a user addressable resource that tracks a stepwise long running operation. +Operations MUST support GET semantics. +The GET operation against an operation MUST return: + +1. The operation resource, it's state, and any extended state relevant to the particular API. +2. 200 OK as the response code. + +Services MAY support operation cancellation by exposing DELETE on the operation. +If supported DELETE operations MUST be idempotent. + +> Note: From an API design perspective, cancellation does not explicitly mean rollback. +On a per-API defined case it may mean rollback, or compensation, or completion, or partial completion, etc. +Following a cancelled operation, It SHOULD NOT be a client's responsibility to return the service to a consistent state which allows continued service. + +Services that do not support operation cancellation MUST return a 405 Method Not Allowed in the event of a DELETE. + +Operations MUST support the following states: + +1. NotStarted +2. Running +3. Succeeded. Terminal State. +4. Failed. Terminal State. + +Services MAY add additional states, such as "Cancelled" or "Partially Completed". Services that support cancellation MUST sufficiently describe their cancellation such that the state of the system can be accurately determined, and any compensating actions may be run. + +Services that support additional states should consider this list of canonical names and avoid creating new names if possible: Cancelling, Cancelled, Aborting, Aborted, Tombstone, Deleting, Deleted. + +An operation MUST contain, and provide in the GET response, the following information: + +1. The timestamp when the operation was created. +2. A timestamp for when the current state was entered. +3. The operation state (notstarted / running / completed). + +Services MAY add additional, API specific, fields into the operation. +The operation status JSON returned looks like: + +```json +{ + "createdDateTime": "2015-06-19T12-01-03.45Z", + "lastActionDateTime": "2015-06-19T12-01-03.45Z", + "status": "notstarted | running | succeeded | failed" +} +``` + +##### Percent complete +Sometimes it is impossible for services to know with any accuracy when an operation will complete. +Which makes using the Retry-After header problematic. +In that case, services MAY include, in the operationStatus JSON, a percent complete field. + +```json +{ + "createdDateTime": "2015-06-19T12-01-03.45Z", + "percentComplete": "50", + "status": "running" +} +``` + +In this example the server has indicated to the client that the long running operation is 50% complete. + +##### Target resource location +For operations that result in, or manipulate, a resource the service MUST include the target resource location in the status upon operation completion. + +```json +{ + "createdDateTime": "2015-06-19T12-01-03.45Z", + "lastActionDateTime": "2015-06-19T12-06-03.0024Z", + "status": "succeeded", + "resourceLocation": "https://api.contoso.com/v1.0/databases/db1" +} +``` + +#### 13.2.6. Operation tombstones +Services MAY choose to support tombstoned operations. +Services MAY choose to delete tombstones after a service defined period of time. + +#### 13.2.7. The typical flow, polling +- Client invokes a stepwise operation by invoking an action using POST +- The server MUST indicate the request has been started by responding with a 202 Accepted status code. The response SHOULD include the location header containing a URL that the client should poll for the results after waiting the number of seconds specified in the Retry-After header. +- Client polls the location until receiving a 200 response with a terminal operation state. + +##### Example of the typical flow, polling +Client invokes the restart action: + +```http +POST https://api.contoso.com/v1.0/databases HTTP/1.1 +Accept: application/json + +{ + "fromFile": "myFile.db", + "color": "red" +} +``` + +The server response indicates the request has been created. + +```http +HTTP/1.1 202 Accepted +Operation-Location: https://api.contoso.com/v1.0/operations/123 +``` + +Client waits for a period of time then invokes another request to try to get the operation status. + +```http +GET https://api.contoso.com/v1.0/operations/123 +Accept: application/json +``` + +Server responds that results are still not ready and optionally provides a recommendation to wait 30 seconds. + +```http +HTTP/1.1 200 OK +Retry-After: 30 + +{ + "createdDateTime": "2015-06-19T12-01-03.4Z", + "status": "running" +} +``` + +Client waits the recommended 30 seconds and then invokes another request to get the results of the operation. + +```http +GET https://api.contoso.com/v1.0/operations/123 +Accept: application/json +``` + +Server responds with a "status:succeeded" operation that includes the resource location. + +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "createdDateTime": "2015-06-19T12-01-03.45Z", + "lastActionDateTime": "2015-06-19T12-06-03.0024Z", + "status": "succeeded", + "resourceLocation": "https://api.contoso.com/v1.0/databases/db1" +} +``` + +#### 13.2.8. The typical flow, push notifications +1. Client invokes a long running operation by invoking an action using POST. The client has a push notification already setup on the parent resource. +2. The service indicates the request has been started by responding with a 202 Accepted status code. The client ignores everything else. +3. Upon completion of the overall operation the service pushes a notification via the subscription on the parent resource. +4. The client retrieves the operation result via the resource URL. + +##### Example of the typical flow, push notifications existing subscription +Client invokes the backup action. +The client already has a push notification subscription setup for db1. + +```http +POST https://api.contoso.com/v1.0/databases/db1?backup HTTP/1.1 +Accept: application/json +``` + +The server response indicates the request has been accepted. + +```http +HTTP/1.1 202 Accepted +Operation-Location: https://api.contoso.com/v1.0/operations/123 +``` + +The caller ignores all the headers in the return. + +The target URL receives a push notification when the operation is complete. + +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "value": [ + { + "subscriptionId": "1234-5678-1111-2222", + "context": "subscription context that was specified at setup", + "resourceUrl": "https://api.contoso.com/v1.0/databases/db1", + "userId" : "contoso.com/user@contoso.com", + "tenantId" : "contoso.com" + } + ] +} +``` + +#### 13.2.9. Retry-After +In the examples above the Retry-After header indicates the number of seconds that the client should wait before trying to get the result from the URL identified by the location header. + +The HTTP specification allows the Retry-After header to alternatively specify a HTTP date, so clients should be prepared to handle this as well. + +```http +HTTP/1.1 202 Accepted +Operation-Location: http://api.contoso.com/v1.0/operations/123 +Retry-After: 60 +``` + +Note: The use of the HTTP Date is inconsistent with the use of ISO 8601 Date Format used throughout this document, but is explicitly defined by the HTTP standard in [RFC 7231][rfc-7231-7-1-1-1]. Services SHOULD prefer the integer number of seconds (in decimal) format over the HTTP date format. + +### 13.3. Retention policy for operation results +In some situations, the result of a long running operation is not a resource that can be addressed. +For example, if you invoke a long running Action that returns a Boolean (rather than a resource). +In these situations, the Location header points to a place where the Boolean result can be retrieved. + +Which begs the question: "How long should operation results be retained?" + +A recommended minimum retention time is 24 hours. + +Operations SHOULD transition to "tombstone" for an additional period of time prior to being purged from the system. + +## 14. Throttling, Quotas, and Limits +### 14.1. Principles +Services should be as responsive as possible, so as not to block callers. +As a rule of thumb any API call that is expected to take longer than 0.5 seconds in the 99th percentile, should consider using the Long-running Operations pattern for those calls. +Obviously, services cannot guarantee these response times in the face of potentially unlimited load from callers. Services should therefore design and document call request limits for clients, and respond with appropriate, actionable errors and error messages if these limits are exceeded. +Services should respond quickly with an error when they are generally overloaded, rather than simply respond slowly. +Finally, many services will have quotas on calls, perhaps a number of operations per hour or day, usually related to a service plan or price. +When these quotas are exceeded services must also provide immediate, actionable errors. +Quotas and Limits should be scoped to a customer unit: a subscription, a tenant, an application, a plan, or without any other identification a range of ip addresses…as appropriate to the service goals so that the load is properly shared and one unit is not interfering with another. + +### 14.2. Return Codes (429 vs 503) +HTTP specifies two return codes for these scenarios: '429 Too Many Requests' and '503 Service Unavailable'. +Services should use 429 for cases where clients are making too many calls and can fix the situation by changing their call pattern. +Services should respond with 503 in cases where general load or other problems outside the control of the individual callers is responsible for the service becoming slow. +In all cases, services should also provide information suggesting how long the callers should wait before trying in again. +Clients should respect these headers and also implement other transient fault handling techniques. +However, there may be clients that simply retry immediately upon failure, potentially increasing the load on the service. +To handle this, services should design so that returning 429 or 503 is as inexpensive as possible, either by putting in special fastpath code, or ideally by depending on a common frontdoor or load balancer that provides this functionality. + +### 14.3. Retry-After and RateLimit Headers +The Retry-After header is the standard way for responding to clients who are being throttled. +It is also common, but optional, in the case of limits and quotas (but not overall system load) to respond with header describing the limit that was exceeded. +However, services across Microsoft and the industry use a wide range of different headers for this purpose. +We recommend using three headers to describe the limit, the number of calls remaining under the limit, and the time when the limit will reset. +However, other headers may be appropriate for specific types of limits. In all cases these must be documented. + +### 14.4. Service Guidance +Services should choose time windows as appropriate for the SLAs or business objectives. +In the case of Quotas, the Retry-After time and time window may be very long (hours, days, weeks, even months. Services use 429 to indicate the specific caller has made too many calls, and 503 to indicate that the service is load shedding but that it is not the caller’s responsibility. + +#### 14.4.1. Responsiveness +1. Services MUST respond quickly in all circumstances, even when under load. +2. Calls that take longer than 1s to respond in the 99th percentile SHOULD use the Long-Running Operation pattern +3. Calls that take longer than 0.5s to respond in the 99th percentile should strongly consider the LRO pattern +4. Services SHOULD NOT introduce sleeps, pauses, etc. that block callers or are not actionable (“tar-pitting”). + +#### 14.4.2. Rate Limits and Quotas +When a caller has made too many calls + +1. Services MUST return a 429 code +2. Services MUST return a standard error response describing the specifics so that a programmer can make appropriate changes +3. Services MUST return a Retry-After header that indicates how long clients should wait before retrying +4. Services MAY return RateLimit headers that document the limit or quota that has been exceeded +5. Services MAY return RateLimit-Limit: the number of calls the client is allowed to make in a time window +6. Services MAY return RateLimit-Remaining: the number of calls remaining in the time window +7. Services MAY return RateLimit-Reset: the time at which the window resets in UTC epoch seconds +8. Services MAY return other service specific RateLimit headers as appropriate for more detailed information or specific limits or quotas + +#### 14.4.3. Overloaded services +When services are generally overloaded and load shedding + +1. Services MUST Return a 503 code +2. Services MUST Return a standard error response (see 7.10.2) describing the specifics so that a programmer can make appropriate changes +3. Services MUST Return a Retry-After header that indicates how long clients should wait before retrying +4. In the 503 case, the service SHOULD NOT return RateLimit headers + +#### 14.4.4. Example Response + +```http +HTTP/1.1 429 Too Many Requests +Content-Type: application/json +Retry-After: 5 +RateLimit-Limit: 1000 +RateLimit-Remaining: 0 +RateLimit-Reset: 1538152773 +{ + "error": { + "code": "requestLimitExceeded", + "message": "The caller has made too many requests in the time period.", + "details": { + "code": "RateLimit", + "limit": "1000", + "remaining": "0", + "reset": "1538152773", + } + } +} +``` + +### 14.5. Caller Guidance +Callers include all users of the API: tools, portals, other services, not just user clients + +1. Callers MUST wait for a minimum of time indicated in a response with a Retry-After before retrying a request. +2. Callers MAY assume that request is retriable after receiving a response with a Retry-After header without making any changes to the request. +3. Clients SHOULD use shared SDKs and common transient fault libraries to implement the proper behavior + +See: https://docs.microsoft.com/en-us/azure/architecture/best-practices/transient-faults + +### 14.6. Handling callers that ignore Retry-After headers +Ideally, 429 and 503 returns are so low cost that even clients that retry immediately can be handled. +In these cases, if possible the service team should make an effort to contact or fix the client. +If it is a known partner, a bug or incident should be filed. +In extreme cases it may be necessary to use DoS style protections such as blocking the caller. + +## 15. Push notifications via webhooks +### 15.1. Scope +Services MAY implement push notifications via web hooks. +This section addresses the following key scenario: + +> Push notification via HTTP Callbacks, often called Web Hooks, to publicly-addressable servers. + +The approach set forth is chosen due to its simplicity, broad applicability, and low barrier to entry for service subscribers. +It's intended as a minimal set of requirements and as a starting point for additional functionality. + +### 15.2. Principles +The core principles for services that support web hooks are: + +1. Services MUST implement at least a poke/pull model. In the poke/pull model, a notification is sent to a client, and clients then send a request to get the current state or the record of change since their last notification. This approach avoids complexities around message ordering, missed messages, and change sets. Services MAY add more data to provide rich notifications. +2. Services MUST implement the challenge/response protocol for configuring callback URLs. +3. Services SHOULD have a recommended age-out period, with flexibility for services to vary based on scenario. +4. Services SHOULD allow subscriptions that are raising successful notifications to live forever and SHOULD be tolerant of reasonable outage periods. +5. Firehose subscriptions MUST be delivered only over HTTPS. Services SHOULD require other subscription types to be HTTPS. See the "Security" section for more details. + +### 15.3. Types of subscriptions +There are two subscription types, and services MAY implement either, both, or none. +The supported subscription types are: + +1. Firehose subscriptions – a subscription is manually created for the subscribing application, typically in an app registration portal. Notifications of activity that any users have consented to the app receiving are sent to this single subscription. +2. Per-resource subscriptions – the subscribing application uses code to programmatically create a subscription at runtime for some user-specific entity(s). + +Services that support both subscription types SHOULD provide differentiated developer experiences for the two types: + +1. Firehose – Services MUST NOT require developers to create code except to directly verify and respond to notifications. Services MUST provide administrative UI for subscription management. Services SHOULD NOT assume that end users are aware of the subscription, only the subscribing application's functionality. +2. Per-user – Services MUST provide an API for developers to create and manage subscriptions as part of their app as well as verifying and responding to notifications. Services MAY expect end users to be aware of subscriptions and MUST allow end users to revoke subscriptions where they were created directly in response to user actions. + +### 15.4. Call sequences +The call sequence for a firehose subscription MUST follow the diagram below. +It shows manual registration of application and subscription, and then the end user making use of one of the service's APIs. +At this part of the flow, two things MUST be stored: + +1. The service MUST store the end user's act of consent to receiving notifications from this specific application (typically a background usage OAUTH scope.) +2. The subscribing application MUST store the end user's tokens in order to call back for details once notified of changes. + +The final part of the sequence is the notification flow itself. + +Non-normative implementation guidance: A resource in the service changes and the service needs to run the following logic: + +1. Determine the set of users who have access to the resource, and could thus expect apps to receive notifications about it on their behalf. +2. See which of those users have consented to receiving notifications and from which apps. +3. See which apps have registered a firehose subscription. +4. Join 1, 2, 3 to produce the concrete set of notifications that must be sent to apps. + +It should be noted that the act of user consent and the act of setting up a firehose subscription could arrive in either order. +Services SHOULD send notifications with setup processed in either order. + +![Firehose subscription setup][websequencediagram-firehose-subscription-setup] + +For a per-user subscription, app registration is either manual or automated. +The call flow for a per-user subscription MUST follow the diagram below. +It shows the end user making use of one of the service's APIs, and again, the same two things MUST be stored: + +1. The service MUST store the end user's act of consent to receiving notifications from this specific application (typically a background usage OAUTH scope). +2. The subscribing application MUST store the end user's tokens in order to call back for details once notified of changes. + +In this case, the subscription is set up programmatically using the end-user's token from the subscribing application. +The app MUST store the ID of the registered subscription alongside the user tokens. + +Non normative implementation guidance: In the final part of the sequence, when an item of data in the service changes and the service needs to run the following logic: + +1. Find the set of subscriptions that correspond via resource to the data that changed. +2. For subscriptions created under an app+user token, send a notification to the app per subscription with the subscription ID and user id of the subscription-creator. +- For subscriptions created with an app only token, check that the owner of the changed data or any user that has visibility of the changed data has consented to notifications to the application, and if so send a set of notifications per user id to the app per subscription with the subscription ID. + + ![User subscription setup][websequencediagram-user-subscription-setup] + +### 15.5. Verifying subscriptions +When subscriptions change either programmatically or in response to change via administrative UI portals, the subscribing service needs to be protected from malicious or unexpected calls from services pushing potentially large volumes of notification traffic. + +For all subscriptions, whether firehose or per-user, services MUST send a verification request as part of creation or modification via portal UI or API request, before sending any other notifications. + +Verification requests MUST be of the following format as an HTTP/HTTPS POST to the subscription's _notificationUrl_. + +```http +POST https://{notificationUrl}?validationToken={randomString} +ClientState: clientOriginatedOpaqueToken (if provided by client on subscription-creation) +Content-Length: 0 +``` + +For the subscription to be set up, the application MUST respond with 200 OK to this request, with the _validationToken_ value as the sole entity body. +Note that if the _notificationUrl_ contains query parameters, the _validationToken_ parameter must be appended with an `&`. + +If any challenge request does not receive the prescribed response within 5 seconds of sending the request, the service MUST return an error, MUST NOT create the subscription, and MUST NOT send further requests or notifications to _notificationUrl_. + +Services MAY perform additional validations on URL ownership. + +### 15.6. Receiving notifications +Services SHOULD send notifications in response to service data changes that do not include details of the changes themselves, but include enough information for the subscribing application to respond appropriately to the following process: + +1. Applications MUST identify the correct cached OAuth token to use for a callback +2. Applications MAY look up any previous delta token for the relevant scope of change +3. Applications MUST determine the URL to call to perform the relevant query for the new state of the service, which MAY be a delta query. + +Services that are providing notifications that will be relayed to end users MAY choose to add more detail to notification packets in order to reduce incoming call load on their service. + Such services MUST be clear that notifications are not guaranteed to be delivered and may be lossy or out of order. + +Notifications MAY be aggregated and sent in batches. +Applications MUST be prepared to receive multiple events inside a single push notification. + +The service MUST send all Web Hook data notifications as POST requests. + +Services MUST allow for a 30-second timeout for notifications. +If a timeout occurs or the application responds with a 5xx response, then the service SHOULD retry the notification with exponential back-off. +All other responses will be ignored. + +The service MUST NOT follow 301/302 redirect requests. + +#### 15.6.1. Notification payload +The basic format for notification payloads is a list of events, each containing the id of the subscription whose referenced resources have changed, the type of change, the resource that should be consumed to identify the exact details of the change and sufficient identity information to look up the token required to call that resource. + +For a firehose subscription, a concrete example of this may look like: + +```json +{ + "value": [ + { + "subscriptionId": "32b8cbd6174ab18b", + "resource": "https://api.contoso.com/v1.0/users/user@contoso.com/files?$delta", + "userId" : "", + "tenantId" : "" + } + ] +} +``` + +For a per-user subscription, a concrete example of this may look like: + +```json +{ + "value": [ + { + "subscriptionId": "32b8cbd6174ab183", + "clientState": "clientOriginatedOpaqueToken", + "expirationDateTime": "2016-02-04T11:23Z", + "resource": "https://api.contoso.com/v1.0/users/user@contoso.com/files/$delta", + "userId" : "", + "tenantId" : "" + }, + { + "subscriptionId": "97b391179fa22", + "clientState ": "clientOriginatedOpaqueToken", + "expirationDateTime": "2016-02-04T11:23Z", + "resource": "https://api.contoso.com/v1.0/users/user@contoso.com/files/$delta", + "userId" : "", + "tenantId" : "" + } + ] +} +``` + +Following is a detailed description of the JSON payload. + +A notification item consists a top-level object that contains an array of events, each of which identified the subscription due to which this notification is being sent. + +Field | Description +----- | -------------------------------------------------------------------------------------------------- +value | Array of events that have been raised within the subscription’s scope since the last notification. + +Each item of the events array contains the following properties: + +Field | Description +------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +subscriptionId | The id of the subscription due to which this notification has been sent.
    Services MUST provide the *subscriptionId* field. +clientState | Services MUST provide the *clientState* field if it was provided at subscription creation time. +expirationDateTime | Services MUST provide the *expirationDateTime* field if the subscription has one. +resource | Services MUST provide the resource field. This URL MUST be considered opaque by the subscribing application. In the case of a richer notification it MAY be subsumed by message content that implicitly contains the resource URL to avoid duplication.
    If a service is providing this data as part of a more detailed data packet, then it need not be duplicated. +userId | Services MUST provide this field for user-scoped resources. In the case of user-scoped resources, the unique identifier for the user should be used.
    In the case of resources shared between a specific set of users, multiple notifications must be sent, passing the unique identifier of each user.
    For tenant-scoped resources, the user id of the subscription should be used. +tenantId | Services that wish to support cross-tenant requests SHOULD provide this field. Services that provide notifications on tenant-scoped data MUST send this field. + +### 15.7. Managing subscriptions programmatically +For per-user subscriptions, an API MUST be provided to create and manage subscriptions. +The API must support at least the operations described here. + +#### 15.7.1. Creating subscriptions +A client creates a subscription by issuing a POST request against the subscriptions resource. +The subscription namespace is client-defined via the POST operation. + +``` +https://api.contoso.com/apiVersion/$subscriptions +``` + +The POST request contains a single subscription object to be created. +That subscription object has the following properties: + +Property Name | Required | Notes +--------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- +resource | Yes | Resource path to watch. +notificationUrl | Yes | The target web hook URL. +clientState | No | Opaque string passed back to the client on all notifications. Callers may choose to use this to provide tagging mechanisms. + +If the subscription was successfully created, the service MUST respond with the status code 201 CREATED and a body containing at least the following properties: + +Property Name | Required | Notes +------------------ | -------- | ------------------------------------------------------------------------------------------- +id | Yes | Unique ID of the new subscription that can be used later to update/delete the subscription. +expirationDateTime | No | Uses existing Microsoft REST API Guidelines defined time formats. + +Creation of subscriptions SHOULD be idempotent. +The combination of properties scoped to the auth token, provides a uniqueness constraint. + +Below is an example request using a User + Application principal to subscribe to notifications from a file: + +```http +POST https://api.contoso.com/files/v1.0/$subscriptions HTTP 1.1 +Authorization: Bearer {UserPrincipalBearerToken} + +{ + "resource": "http://api.service.com/v1.0/files/file1.txt", + "notificationUrl": "https://contoso.com/myCallbacks", + "clientState": "clientOriginatedOpaqueToken" +} +``` + +The service SHOULD respond to such a message with a response format minimally like this: + +```json +{ + "id": "32b8cbd6174ab18b", + "expirationDateTime": "2016-02-04T11:23Z" +} +``` + +Below is an example using an Application-Only principal where the application is watching all files to which it's authorized: + +```http +POST https://api.contoso.com/files/v1.0/$subscriptions HTTP 1.1 +Authorization: Bearer {ApplicationPrincipalBearerToken} + +{ + "resource": "All.Files", + "notificationUrl": "https://contoso.com/myCallbacks", + "clientState": "clientOriginatedOpaqueToken" +} +``` + +The service SHOULD respond to such a message with a response format minimally like this: + +```json +{ + "id": "8cbd6174abb391179", + "expirationDateTime": "2016-02-04T11:23Z" +} +``` + +#### 15.7.2. Updating subscriptions +Services MAY support amending subscriptions. + To update the properties of an existing subscription, clients use PATCH requests providing the ID and the properties that need to change. +Omitted properties will retain their values. +To delete a property, assign a value of JSON null to it. + +As with creation, subscriptions are individually managed. + +The following request changes the notification URL of an existing subscription: + +```http +PATCH https://api.contoso.com/files/v1.0/$subscriptions/{id} HTTP 1.1 +Authorization: Bearer {UserPrincipalBearerToken} + +{ + "notificationUrl": "https://contoso.com/myNewCallback" +} +``` + +If the PATCH request contains a new _notificationUrl_, the server MUST perform validation on it as described above. +If the new URL fails to validate, the service MUST fail the PATCH request and leave the subscription in its previous state. + +The service MUST return an empty body and `204 No Content` to indicate a successful patch. + +The service MUST return an error body and status code if the patch failed. + +The operation MUST succeed or fail atomically. + +#### 15.7.3. Deleting subscriptions +Services MUST support deleting subscriptions. +Existing subscriptions can be deleted by making a DELETE request against the subscription resource: + +```http +DELETE https://api.contoso.com/files/v1.0/$subscriptions/{id} HTTP 1.1 +Authorization: Bearer {UserPrincipalBearerToken} +``` + +As with update, the service MUST return `204 No Content` for a successful delete, or an error body and status code to indicate failure. + +#### 15.7.4. Enumerating subscriptions +To get a list of active subscriptions, clients issue a GET request against the subscriptions resource using a User + Application or Application-Only bearer token: + +```http +GET https://api.contoso.com/files/v1.0/$subscriptions HTTP 1.1 +Authorization: Bearer {UserPrincipalBearerToken} +``` + +The service MUST return a format as below using a User + Application principal bearer token: + +```json +{ + "value": [ + { + "id": "32b8cbd6174ab18b", + "resource": " http://api.contoso.com/v1.0/files/file1.txt", + "notificationUrl": "https://contoso.com/myCallbacks", + "clientState": "clientOriginatedOpaqueToken", + "expirationDateTime": "2016-02-04T11:23Z" + } + ] +} +``` + +An example that may be returned using Application-Only principal bearer token: + +```json +{ + "value": [ + { + "id": "6174ab18bfa22", + "resource": "All.Files ", + "notificationUrl": "https://contoso.com/myCallbacks", + "clientState": "clientOriginatedOpaqueToken", + "expirationDateTime": "2016-02-04T11:23Z" + } + ] +} +``` + +### 15.8. Security +All service URLs must be HTTPS (that is, all inbound calls MUST be HTTPS). Services that deal with Web Hooks MUST accept HTTPS. + +We recommend that services that allow client defined Web Hook Callback URLs SHOULD NOT transmit data over HTTP. +This is because information can be inadvertently exposed via client, network, server logs and other mechanisms. + +However, there are scenarios where the above recommendations cannot be followed due to client endpoint or software limitations. +Consequently, services MAY allow web hook URLs that are HTTP. + +Furthermore, services that allow client defined HTTP web hooks callback URLs SHOULD be compliant with privacy policy specified by engineering leadership. +This will typically include recommending that clients prefer SSL connections and adhere to special precautions to ensure that logs and other service data collection are properly handled. + +For example, services may not want to require developers to generate certificates to onboard. +Services might only enable this on test accounts. + +## 16. Unsupported requests +RESTful API clients MAY request functionality that is currently unsupported. +RESTful APIs MUST respond to valid but unsupported requests consistent with this section. + +### 16.1. Essential guidance +RESTful APIs will often choose to limit functionality that can be performed by clients. +For instance, auditing systems allow records to be created but not modified or deleted. +Similarly, some APIs will expose collections but require or otherwise limit filtering and ordering criteria, or MAY not support client-driven pagination. + +### 16.2. Feature allow list +If a service does not support any of the below API features, then an error response MUST be provided if the feature is requested by a caller. +The features are: +- Key Addressing in a collection, such as: `https://api.contoso.com/v1.0/people/user1@contoso.com` +- Filtering a collection by a property value, such as: `https://api.contoso.com/v1.0/people?$filter=name eq 'david'` +- Filtering a collection by range, such as: `http://api.contoso.com/v1.0/people?$filter=hireDate ge 2014-01-01 and hireDate le 2014-12-31` +- Client-driven pagination via $top and $skip, such as: `http://api.contoso.com/v1.0/people?$top=5&$skip=2` +- Sorting by $orderBy, such as: `https://api.contoso.com/v1.0/people?$orderBy=name desc` +- Providing $delta tokens, such as: `https://api.contoso.com/v1.0/people?$delta` + +#### 16.2.1. Error response +Services MUST provide an error response if a caller requests an unsupported feature found in the feature allow list. +The error response MUST be an HTTP status code from the 4xx series, indicating that the request cannot be fulfilled. +Unless a more specific error status is appropriate for the given request, services SHOULD return "400 Bad Request" and an error payload conforming to the error response guidance provided in the Microsoft REST API Guidelines. +Services SHOULD include enough detail in the response message for a developer to determine exactly what portion of the request is not supported. + +Example: + +```http +GET https://api.contoso.com/v1.0/people?$orderBy=name HTTP/1.1 +Accept: application/json +``` + +```http +HTTP/1.1 400 Bad Request +Content-Type: application/json + +{ + "error": { + "code": "ErrorUnsupportedOrderBy", + "message": "Ordering by name is not supported." + } +} +``` + +## 17. Naming guidelines +### 17.1. Approach +Naming policies should aid developers in discovering functionality without having to constantly refer to documentation. +Use of common patterns and standard conventions greatly aids developers in correctly guessing common property names and meanings. +Services SHOULD use verbose naming patterns and SHOULD NOT use abbreviations other than acronyms that are the dominant mode of expression in the domain being represented by the API, (e.g. Url). + +### 17.2. Casing +- Acronyms SHOULD follow the casing conventions as though they were regular words (e.g. Url). +- All identifiers including namespaces, entityTypes, entitySets, properties, actions, functions and enumeration values SHOULD use lowerCamelCase. +- HTTP headers are the exception and SHOULD use standard HTTP convention of Capitalized-Hyphenated-Terms. + +### 17.3. Names to avoid +Certain names are so overloaded in API domains that they lose all meaning or clash with other common usages in domains that cannot be avoided when using REST APIs, such as OAUTH. +Services SHOULD NOT use the following names: +- Context +- Scope +- Resource + +### 17.4. Forming compound names +- Services SHOULD avoid using articles such as 'a', 'the', 'of' unless needed to convey meaning. + - e.g. names such as aUser, theAccount, countOfBooks SHOULD NOT be used, rather user, account, bookCount SHOULD be preferred. +- Services SHOULD add a type to a property name when not doing so would cause ambiguity about how the data is represented or would cause the service not to use a common property name. +- When adding a type to a property name, services MUST add the type at the end, e.g. createdDateTime. + +### 17.5. Identity properties +- Services MUST use string types for identity properties. +- For OData services, the service MUST use the OData @id property to represent the canonical identifier of the resource. +- Services MAY use the simple 'id' property to represent a local or legacy primary key value for a resource. +- Services SHOULD use the name of the relationship postfixed with 'Id' to represent a foreign key to another resource, e.g. subscriptionId. + - The content of this property SHOULD be the canonical ID of the referenced resource. + +### 17.6. Date and time properties + +- For properties requiring both date and time, services MUST use the suffix 'DateTime'. +- For properties requiring only date information without specifying time, services MUST use the suffix 'Date', e.g. birthDate. +- For properties requiring only time information without specifying date, services MUST use the suffix 'Time', e.g. appointmentStartTime. + +### 17.7. Name properties +- For the overall name of a resource typically shown to users, services MUST use the property name 'displayName'. +- Services MAY use other common naming properties, e.g. givenName, surname, signInName. + +### 17.8. Collections and counts +- Services MUST name collections as plural nouns or plural noun phrases using correct English. +- Services MAY use simplified English for nouns that have plurals not in common verbal usage. + - e.g. schemas MAY be used instead of schemata. +- Services MUST name counts of resources with a noun or noun phrase suffixed with 'Count'. + +### 17.9. Common property names +Where services have a property, whose data matches the names below, the service MUST use the name from this table. +This table will grow as services add terms that will be more commonly used. +Service owners adding such terms SHOULD propose additions to this document. + +| | +|------------- | + attendees | + body | + createdDateTime | + childCount | + children | + contentUrl | + country | + createdBy | + displayName | + errorUrl | + eTag | + event | + expirationDateTime | + givenName | + jobTitle | + kind | + id | + lastModifiedDateTime | + location | + memberOf | + message | + name | + owner | + people | + person | + postalCode | + photo | + preferredLanguage | + properties | + signInName | + surname | + tags | + userPrincipalName | + webUrl | + +## 18. Appendix +### 18.1. Sequence diagram notes +All sequence diagrams in this document are generated using the [WebSequenceDiagrams.com](https://www.websequencediagrams.com/). To generate them, paste the text below into the web tool. + +#### 18.1.1. Push notifications, per user flow + +``` +=== Begin Text === +note over Developer, Automation, App Server: + An App Developer like MovieMaker + Wants to integrate with primary service like Dropbox +end note +note over DB Portal, DB App Registration, DB Notifications, DB Auth, DB Service: The primary service like Dropbox +note over Client: The end users' browser or installed app + +note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Manual App Registration + + +Developer <--> DB Portal : Login into Portal, App Registration UX +DB Portal -> +DB App Registration: App Name etc. +note over DB App Registration: Confirm Portal Access Token + +DB App Registration -> -DB Portal: App ID +DB Portal <--> App Server: Developer copies App ID + +note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Manual Notification Registration + +Developer <--> DB Portal: webhook registration UX +DB Portal -> +DB Notifications: Register: App Server webhook URL, Scope, App ID +Note over DB Notifications : Confirm Portal Access Token +DB Notifications -> -DB Portal: notification ID +DB Portal --> App Server : Developer may copy notification ID + + +note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Client Authorization + +Client -> +App Server : Request access to DB protected information +App Server -> -Client : Redirect to DB Authorization endpoint with authorization request +Client -> +DB Auth : Redirected authorization request +Client <--> DB Auth : Authorization UX +DB Auth -> -Client : Redirect back to App Server with code +Client -> +App Server : Redirect request back to access server with access code +App Server -> +DB Auth : Request tokens with access code +note right of DB Service: Cache that this User ID provided access to App ID +DB Auth -> -App Server : Response with access, refresh, and ID tokens +note right of App Server : Cache tokens by user ID +App Server -> -Client : Return information to client + +note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Notification Flow + +Client <--> DB Service: Changes to user data - typical via interacting with App Server via Client +DB Service -> App Server : Notification with notification ID and user ID +App Server -> +DB Service : Request changed information with cached access tokens and "since" token +note over DB Service: Confirm User Access Token +DB Service -> -App Server : Response with data and new "since" token +note right of App Server: Update status and cache new "since" token +=== End Text === +``` + +#### 18.1.2. Push notifications, firehose flow + +``` +=== Begin Text === +note over Developer, Automation, App Server: + An App Developer like MovieMaker + Wants to integrate with primary service like Dropbox +end note +note over DB Portal, DB App Registration, DB Notifications, DB Auth, DB Service: The primary service like Dropbox +note over Client: The end users' browser or installed app + +note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : App Registration + +alt Automated app registration + Developer <--> Automation: Configure + Automation -> +DB App Registration: App Name etc. + note over DB App Registration: Confirm App Access Token + DB App Registration -> -Automation: App ID, App Secret + Automation --> App Server : Embed App ID, App Secret +else Manual app registration + Developer <--> DB Portal : Login into Portal, App Registration UX + DB Portal -> +DB App Registration: App Name etc. + note over DB App Registration: Confirm Portal Access Token + + DB App Registration -> -DB Portal: App ID + DB Portal <--> App Server: Developer copies App ID +end + +note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Client Authorization + +Client -> +App Server : Request access to DB protected information +App Server -> -Client : Redirect to DB Authorization endpoint with authorization request +Client -> +DB Auth : Redirected authorization request +Client <--> DB Auth : Authorization UX +DB Auth -> -Client : Redirect back to App Server with code +Client -> +App Server : Redirect request back to access server with access code +App Server -> +DB Auth : Request tokens with access code +note right of DB Service: Cache that this User ID provided access to App ID +DB Auth -> -App Server : Response with access, refresh, and ID tokens +note right of App Server : Cache tokens by user ID +App Server -> -Client : Return information to client + + + +note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Notification Registration + +App Server->+DB Notifications: Register: App server webhook URL, Scope, App ID +note over DB Notifications : Confirm User Access Token +DB Notifications -> -App Server: notification ID +note right of App Server : Cache the Notification ID and User Access Token + + + +note over Developer, Automation, App Server, DB Portal, DB App Registration, DB Notifications, Client : Notification Flow + +Client <--> DB Service: Changes to user data - typical via interacting with App Server via Client +DB Service -> App Server : Notification with notification ID and user ID +App Server -> +DB Service : Request changed information with cached access tokens and "since" token +note over DB Service: Confirm User Access Token +DB Service -> -App Server : Response with data and new "since" token +note right of App Server: Update status and cache new "since" token + + + +=== End Text === +``` +[fielding]: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm +[IANA-headers]: https://www.iana.org/assignments/message-headers/message-headers.xhtml +[rfc-2119]: https://tools.ietf.org/html/rfc2119 +[rfc7231-7-1-1-1]: https://tools.ietf.org/html/rfc7231#section-7.1.1.1 +[rfc-7230-3-1-1]: https://tools.ietf.org/html/rfc7230#section-3.1.1 +[rfc-7231]: https://tools.ietf.org/html/rfc7231 +[rest-in-practice]: https://www.amazon.com/REST-Practice-Hypermedia-Systems-Architecture/dp/0596805829/ +[rest-on-wikipedia]: https://en.wikipedia.org/wiki/Representational_state_transfer +[rfc-5789]: https://tools.ietf.org/html/rfc5789 +[rfc-5988]: https://tools.ietf.org/html/rfc5988 +[rfc-3339]: https://tools.ietf.org/html/rfc3339 +[rfc-5322-3-3]: https://tools.ietf.org/html/rfc5322#section-3.3 +[cors-preflight]: https://www.w3.org/TR/cors/#resource-preflight-requests +[rfc-3864]: https://tools.ietf.org/html/rfc3864 +[odata-json-annotations]: https://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Instance_Annotations +[cors]: https://www.w3.org/TR/access-control/ +[cors-user-credentials]: https://www.w3.org/TR/access-control/#user-credentials +[cors-simple-headers]: https://www.w3.org/TR/access-control/#simple-header +[rfc-4627]: https://tools.ietf.org/html/rfc4627 +[iso-8601]: https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 +[clr-time]: https://msdn.microsoft.com/en-us/library/System.DateTime(v=vs.110).aspx +[ecmascript-time]: https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.1 +[ole-date]: https://docs.microsoft.com/en-us/windows/desktop/api/oleauto/nf-oleauto-varianttimetosystemtime +[ticks-time]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx +[unix-time]: https://msdn.microsoft.com/en-us/library/1f4c8f33.aspx +[windows-time]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx +[excel-time]: https://support.microsoft.com/kb/214326?wa=wsignin1.0 +[wikipedia-iso8601-durations]: https://en.wikipedia.org/wiki/ISO_8601#Durations +[wikipedia-iso8601-intervals]: https://en.wikipedia.org/wiki/ISO_8601#Time_intervals +[wikipedia-iso8601-repeatingintervals]: https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals +[principle-of-least-astonishment]: https://en.wikipedia.org/wiki/Principle_of_least_astonishment +[odata-breaking-changes]: https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398209 +[websequencediagram-firehose-subscription-setup]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=bm90ZSBvdmVyIERldmVsb3BlciwgQXV0b21hdGlvbiwgQXBwIFNlcnZlcjogCiAgICAgQW4AEAUAJwkgbGlrZSBNb3ZpZU1ha2VyACAGV2FudHMgdG8gaW50ZWdyYXRlIHdpdGggcHJpbWFyeSBzZXJ2aWNlADcGRHJvcGJveAplbmQgbm90ZQoAgQwLQiBQb3J0YWwsIERCAIEJBVJlZ2lzdHIAgRkHREIgTm90aWZpYwCBLAVzACEGdXRoACsFUwBgBjogVGhlAF0eAIF_CkNsaWVudAAtBmVuZCB1c2VycycgYnJvd3NlciBvciBpbnN0YWxsZWQgYXBwCgCBIQwAgiQgAIFABQCBIS8AgQoGIDogTWFudWFsAIFzEQoKCgCDAgo8LS0-AIIqCiA6IExvZ2luIGludG8Agj8JAII1ECBVWCAKACoKLT4gKwCCWBM6AIQGBU5hbWUgZXRjLgCDFQ4AGxJDb25maXJtAIEBCEFjY2VzcyBUb2tlbgoKAIM3EyAtPiAtAINkCQBnBklEAIEMCwCBVQUAhQIMAIR3CmNvcGllcwArCACCIHAAhHMMAIMKDwCDABg6IHdlYmhvb2sgcgCCeg4AgnUSAIVQDToAhXYHZXIAgwgGAIcTBgBECVVSTCwgU2NvcGUAhzIGSUQKTgCGPQwAhhwNIACDBh4AHhEAgxEPbgCBagwAgxwNAIMaDiAAgx0MbWF5IGNvcHkALREAhVtqAIZHB0F1dGhvcml6AIY7BwCGXQctPiArAIEuDVJlcXVlc3QgYQCFOQZ0byBEQiBwcm90ZWN0ZWQgaW5mb3IAiiQGCgCDBQstPiAtAIctCVJlZGlyZWN0ADYHAGwNIGVuZHBvaW50AIoWBmEADw1yAHYGAIEQDACJVAcASwtlZAAYHgCICAgAMAcAcA4AhGoGAE0FAIEdFmJhY2sgdG8AhF8NaXRoIGNvZGUAghoaaQCBagcAgToHAD0JAII-B3MAPgsAglEHAEsFAIIzDgCBXw0Agn8GdG9rZW5zACcSAI0_BXJpZ2h0IG9mAItpDUNhY2hlIHRoYXQgdGhpcyBVc2VyIElEIHByb3ZpZGVkAINNCwCIZgoAggcJAIN7D3Nwb25zAI0_BwCECgYsIHJlZnJlc2gsIGFuZCBJRACBHAcAgQMPAIYADQCBDAcAgUUGYnkAjFkFIElEAIQkG3R1cm4AhF4MIHRvIGMAjR8FAIwRagCJVw1GbG93AIYqCQCMaQgAgmoKaGFuZ2UAj3YFAIFXBWRhdGEgLSB0eXBpY2FsIHZpYQCQDgVyYWN0aW5nAJAPBgCJQQt2aWEAjnsHCgCPNgogAIhDEACKZw0AkFMFAIkBDwCDDAUAgkYWKwBNCwCHWApjAIEyBQCHRg0AhWUHYWNoAIQeDACEfwVhbmQgInNpbmNlIgCFEQYAkSQOAIR3CgCNfwcAhHQFAIpQEACBUgsAhFAcAII8BWFuZCBuZXcAYRQAhFUTOiBVcGRhdGUgc3RhdHUAgSkGAIFDBQAxEwoKCg&s=mscgen +[websequencediagram-user-subscription-setup]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=bm90ZSBvdmVyIERldmVsb3BlciwgQXV0b21hdGlvbiwgQXBwIFNlcnZlcjogCiAgICAgQW4AEAUAJwkgbGlrZSBNb3ZpZU1ha2VyACAGV2FudHMgdG8gaW50ZWdyYXRlIHdpdGggcHJpbWFyeSBzZXJ2aWNlADcGRHJvcGJveAplbmQgbm90ZQoAgQwLQiBQb3J0YWwsIERCAIEJBVJlZ2lzdHIAgRkHREIgTm90aWZpYwCBLAVzACEGdXRoACsFUwBgBjogVGhlAF0eAIF_CkNsaWVudAAtBmVuZCB1c2VycycgYnJvd3NlciBvciBpbnN0YWxsZWQgYXBwCgCBIQwAgiQgAIFABQCBIS8AgQoGIDoAgWwRCgphbHQAgyUIAIEHBiByABQMICAAgxsLPC0tPgCDTws6IENvbmZpZ3VyZQogIACDaAsgLT4gKwCCWBMAegZOYW1lIGV0Yy4AhAgFAIMaDQAfEgBdBXJtAIQ_BUFjY2VzcyBUb2tlAIETBgCDOxIgLT4gLQCBFgxBcHAgSUQAhHwIY3JldACBGxAtPgCFFgsgOiBFbWJlZAAkFGVsc2UgTWFudWFsAIIEJACEbQkgOiBMb2dpbiBpbnRvAIUBCQCBKRFVWACGGAUALQoAgh8mAIIZKwCBCAcAgjoNAIIsHACGLwkAgj8IAIESDgCECAYAh1ELAIdFCmNvcGllcwAuCGVuZACEeGoAhWQHQXV0aG9yaXoAhV8HAIV6By0-ICsAg2ANUmVxdWVzdCBhAIRVBnRvIERCIHByb3RlY3RlZCBpbmZvcgCJQQYKAIQaCy0-IC0AhkoJUmVkaXJlY3QANgcAbA0gZW5kcG9pbnQAiTMGYQAPDXIAdgYAgRAMAIhxBwBLC2VkABgeAIRjCAAwB0EAcQxVWAoASQgAgRwWYmFjayB0bwCFdAwAilwFY29kZQCCGRppAIFpBwCBOQcAPQkAgj0HcwA-CwCCUAcASwUAgjIOAIFeDQCCfgZ0b2tlbnMAJxIAjFsFcmlnaHQgb2YAiwUNQ2FjaGUgdGhhdCB0aGlzIFVzZXIgSUQgcHJvdmlkZWQAg0wLAIU6BwCCBAwAg3oPc3BvbnMAjFsHAIQJBiwgcmVmcmVzaCwgYW5kIElEAIEcBwCBAw8AiDENAIEMBwCBRQZieQCLdQUgSUQAhCMbdHVybgCEXQwgdG8gYwCMOwUKCgCLL2oAjXUMAIwTDwCPNQotPisAjhwQOgCORQdlcgCMVwYAg3YIZWJob29rIFVSTCwgU2NvcGUAkAEGSUQAjwoOAI5rDSAAi2UKAINFBQCLYw0AHBEAgzUOOiBuAIE2DABgCACDCB1oZQCBaQ5JRACDYwUAahIAghB4RmxvdwCJMwkAjE0IAIV0CmhhbmdlAJIcBQCEYQVkYXRhIC0gdHlwaWNhbCB2aWEAkjQFcmFjdGluZwCSNQYAjV8LdmlhAJEhBwoAkVwKIACNfhAAhAsNAJJ5BQCCWQ8AhhYFAIVQFisATQsAimEKYwCBMgUAik8NAIhvB2FjaACHKAwAiAkFYW5kICJzaW5jZSIAiBsGAJNKDgCIAQoAhB0cAIFSCwCHWhwAgjwFYW5kIG5ldwBhFACHXxM6IFVwZGF0ZSBzdGF0dQCBKQYAgUMFADETCgoK&s=mscgen diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index a1879938..545bc7e4 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -10,11 +10,11 @@ Table of contents - [Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) - [Resource modeling patterns](#resource-modeling-patterns) - [Pros and cons](#pros-and-cons) + - [Nullable properties](#nullable-properties) - [Query support](#query-support) - [Behavior modeling](#behavior-modeling) - [Error handling](#error-handling) - - [Enums](#enums) - - [External Standards](#external-standards) + - [External standards](#external-standards) - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) - [Versioning and deprecation](#versioning-and-deprecation) - [Recommended API design patterns](#recommended-api-design-patterns) @@ -34,8 +34,7 @@ ensure that Microsoft Graph has a consistent and easy-to-use API surface. A new - Be sustainable and evolvable by using clear API contracts. -The Microsoft Graph REST API Guidelines are an extension of the -[Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). It is assumed that readers are following the Microsoft REST API Guidelines except where this document outlines specific differences or exceptions to those guidelines. Together, these guidelines and a library of API patterns serve as the means by which API teams discuss and come to consensus on API review requirements. +The Microsoft Graph REST API Guidelines consist of a concise overview document, a collection of articles on Graph standards, and a library of patterns that provide best practices for resolving common API design problems.Together, these documents serve as the means by which API teams discuss and come to consensus on API review requirements. Technology and software are constantly changing and evolving, and as such, this is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface. Consequently, this document will frequently change to add guidance in areas previously uncovered or to clarify existing guidance. It will less frequently change the directional guidance it has already provided. To suggest a change or propose a new idea, @@ -91,7 +90,7 @@ At every step of your design, you need to consider security, privacy, and compli ### Naming -API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API naming guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#17-naming-guidelines). +API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API naming guidelines](Guidelines.md#17-naming-guidelines). Following is a short summary of the most often used conventions. @@ -155,7 +154,7 @@ Effectively, top-level categories define a perimeter for the API surface; thus, You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type does not. In Microsoft Graph, this key property is called `id` for server-created key values. If there is a natural name for the key property, then the workload can use that. -Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must use entity types to model addressable resources such as individually addressable items within a collection. For more information, see the [Microsoft REST API Guidelines collection URL patterns](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. +Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must use entity types to model addressable resources such as individually addressable items within a collection. For more information, see the [Microsoft REST API Guidelines collection URL patterns](Guidelines-deprecated.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. ```xml @@ -227,11 +226,11 @@ Following are a few pros and cons to decide which pattern to use: The facet and flat bag approaches often require nullable properties, so it is important to still use non-nullable properties where appropriate. Since inheritance can often remove the use of nullable properties completely, it is also important to know when nullable properties are necessary. -See [Nullable properties](./nullable.md) for more details. +See [Nullable properties](./articles/nullable.md) for more details. ### Query support -Microsoft Graph APIs should support basic query options in conformance with OData specifications and [Microsoft REST API Guidelines for error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses). +Microsoft Graph APIs returning collections of resources should support basic query options in conformance with [OData specifications](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody) and [Microsoft REST API Guidelines](Guidelines-deprecated.md#9-collections). |Requirements | |----------------------------------------------------------------------------------------------------| @@ -239,8 +238,8 @@ Microsoft Graph APIs should support basic query options in conformance with ODat | :ballot_box_with_check: **SHOULD** support `/entityTypeCollection/{id}?$expand=navProp1` option for navigation properties of entities. | | :ballot_box_with_check: **SHOULD** support `$filter` with `eq` and `ne` operations on properties of entity collections. | | :heavy_check_mark: **MUST** support pagination of collections (of entity types or complex types) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | -| :ballot_box_with_check: **MAY** support [server-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#981-server-driven-paging) of collections using `$skiptoken`. | -| :ballot_box_with_check: **SHOULD** support [client-driven pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#982-client-driven-paging) of collections using `$top` and `$skip`. | +| :ballot_box_with_check: **MAY** support [server-driven pagination](Guidelines-deprecated.md#981-server-driven-paging) of collections using `$skiptoken`. | +| :ballot_box_with_check: **SHOULD** support [client-driven pagination](Guidelines-deprecated.md#982-client-driven-paging) of collections using `$top` and `$skip`. | | :ballot_box_with_check: **SHOULD** support `$count` for collections. | | :ballot_box_with_check: **SHOULD** support sorting with `$orderby` both ascending and descending on properties of the entities. | @@ -267,7 +266,7 @@ If possible, APIs SHOULD use resource-based designs with standard HTTP methods r Operation resources must have a binding parameter that matches the type of the bound resource. In addition, both actions and functions support overloading, meaning an API definition might contain multiple actions or functions with the same name. -For a complete list of standard HTTP operations, see the [Microsoft REST API Guidelines error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses). +For a complete list of standard HTTP operations, see the [Microsoft REST API Guidelines error condition responses](Guidelines-deprecated.md#7102-error-condition-responses). ### Error handling @@ -345,7 +344,7 @@ Note that it is backwards compatible for a workload to migrate from the second a ## API contract and non-backward compatible changes The Microsoft Graph definition of breaking changes is based on the -[Microsoft REST API Guidelines definition of a breaking change](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#123-definition-of-a-breaking-change). In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and is not considered a breaking change. +[Microsoft REST API Guidelines definition of a breaking change](Guidelines-deprecated.md#123-definition-of-a-breaking-change). In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and is not considered a breaking change. **Non-breaking changes:** @@ -388,7 +387,7 @@ Detailed requirements for versioning and deprecation are described in the [Depre ## Recommended API design patterns -The guidelines in previous sections are intentionally brief and provide a jump start for Microsoft Graph API developers. More detailed design guidance about REST APIs is published at the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/). Microsoft Graph-specific patterns are outlined in the following table. +The guidelines in previous sections are intentionally brief and provide a jump start for Microsoft Graph API developers. More detailed design guidance about REST APIs is published at the [Microsoft REST API Guidelines](Guidelines-deprecated.md). Microsoft Graph-specific patterns are outlined in the following table. | Pattern | Description | |--------------------------------------------------|----------------------------------------------------------------------------| diff --git a/graph/articles/nullable.md b/graph/articles/nullable.md new file mode 100644 index 00000000..7e581620 --- /dev/null +++ b/graph/articles/nullable.md @@ -0,0 +1,311 @@ +# Nullable Properties + +A nullable property means *only* that the property may have `null` as a value; the "nullability" of a property does not say anything about how a value is set into a property. +For example, a non-nullable property is *not* required to create a new instance of an entity. +It only means that the property will have a value when it is retrieved. +In the case that no value is provided when the entity is created, this means that the service will create one; this value can be specified with the `DefaultValue` attribute, but if the value is contextual and determine at request time, then the property can both be non-nullable *and* have no `DefaultValue` specified. +Below are some examples of nullable and non-nullable properties. + +## CSDL + +```xml + +... + + + + + + + + + + ... + +``` + +## HTTP Requests + +### {1} Create a servicePrincipal with no properties + +```HTTP +POST /servicePrincipals + +400 Bad Request +{ + "error": { + "code": "badRequest", + "message": "The 'appId' property is required to create a servicePrincipal." + } +} +``` + +### {2} Create a servicePrincipal without a display name + +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001" +} + +201 Created +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "some application name", + "foo": "testval", + "bar": "differentvalue", + ... +} +``` +Notes: +1. `displayName` was given a value by the service even though no value was provided by the client +2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL +3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL + +### {3} Update the display name of a service principal to null + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "displayName": null +} + +400 Bad Request +{ + "error": { + "code": "badRequest", + "message": "null is not a valid value for the property 'displayName'; 'displayName' is not a nullable property." + } +} +``` +Notes: +1. `displayName` cannot be set to `null` because it has be marked with `Nullable="false"` in the CSDL. + +### {4} Update the display name of a service principal + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "displayName": "a non-generated display name" +} + +200 OK +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a non-generated display name", + "foo": "testval", + "bar": "differentvalue", + ... +} +``` +Notes: +1. `displayName` can be set to any value other than `null` +2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. + +### {5} Update the foo property of a service principal to null + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "foo": null +} + +200 OK +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a non-generated display name", + "foo": null, + "bar": "differentvalue", + ... +} +``` +Notes: +1. `foo` can be set to `null` because it has be marked with `Nullable="true"` in the CSDL. +2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. + +### {6} Update the foo property of a service principal to a non-default value + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "foo": "something other than testval" +} + +200 OK +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a non-generated display name", + "foo": "something other than testval", + "bar": "differentvalue", + ... +} +``` +Notes: +1. `foo` can be set to `something other than testval` +2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. + +### {7} Update the bar property of a service principal to null + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "bar": null +} + +400 Bad Request +{ + "error": { + "code": "badRequest", + "message": "null is not a valid value for the property 'bar'; 'bar' is not a nullable property." + } +} +``` +Notes: +1. `bar` cannot be set to `null` because it has be marked with `Nullable="false"` in the CSDL. + +### {8} Update the bar property of a service principal to a non-default value + +```HTTP +PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 +{ + "bar": "a new bar" +} + +200 OK +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a non-generated display name", + "foo": "something other than testval", + "bar": "a new bar", + ... +} +``` +Notes: +1. `bar` can be set to `a new bar` +2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. + +### {9} Create a service principal while customizing the display name +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a different name" +} + +201 Created +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "a different name", + "foo": "testval", + "bar": "differentvalue", + ... +} +``` +Notes: +1. `displayName` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. +2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL +3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL + +### {10} Create a service principal with a null display name +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": null +} + +400 Bad Request +{ + "error": { + "code": "badRequest", + "message": "null is not a valid value for the property 'displayName'; 'displayName' is not a nullable property." + } +} +``` +Notes: +1. `displayName` isn't required to create a new `servicePrincipal`, but it *can* be provided; it *cannot* be provided as `null` because the property was marked with `Nullable="false"` + +### {11} Create a service principal with a value for the foo property +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "foo": "a foo value on creation" +} + +201 Created +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "some application name", + "foo": "a foo value on creation", + "bar": "differentvalue", + ... +} +``` +Notes: +1. `displayName` was given a value by the service even though no value was provided by the client +2. `foo` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. +3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL + +### {12} Create a service principal with null for the foo property +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "foo": null +} + +201 Created +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "some application name", + "foo": null, + "bar": "differentvalue", + ... +} +``` +Notes: +1. `displayName` was given a value by the service even though no value was provided by the client +2. `foo` isn't required to create a new `servicePrincipal`, but it *can* be provided; because the property has `Nullable="true"`, a `null` value can be provided for it. +3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL + +### {13} Create a service principal with a value for the bar property +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "bar": "running out of ideas for value names" +} + +201 Created +{ + "appId": "00000000-0000-0000-0000-000000000001", + "displayName": "some application name", + "foo": "testval", + "bar": "running out of ideas for value names", + ... +} +``` +Notes: +1. `displayName` was given a value by the service even though no value was provided by the client +2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL +3. `bar` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. + +### {14} Create a service principal with null for the bar property +```HTTP +POST /servicePrincipals +{ + "appId": "00000000-0000-0000-0000-000000000001", + "bar": null +} + +400 Bad Request +{ + "error": { + "code": "badRequest", + "message": "null is not a valid value for the property 'bar'; 'bar' is not a nullable property." + } +} +``` +Notes: +1. `bar` isn't required to create a new `servicePrincipal`, but it *can* be provided; it *cannot* be provided as `null` because the property was marked with `Nullable="false"` From 2ad6aedbe19779ef8aaa14df34711034abf176da Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 18 Dec 2023 18:32:03 -0500 Subject: [PATCH 628/729] merging RESt --- graph/Guidelines-deprecated.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph/Guidelines-deprecated.md b/graph/Guidelines-deprecated.md index 0cc804ad..1264cfcb 100644 --- a/graph/Guidelines-deprecated.md +++ b/graph/Guidelines-deprecated.md @@ -1,13 +1,13 @@ -# Microsoft REST API Guidelines -> # NOTICE TO READERS + +> # DEPRECATION NOTICE TO READERS > This document has been deprecated and has been merged with [Microsoft Graph REST API Guidelines](./graph/Guidelines_deprecated.md). Please refer to the notes below for the latest guidance. > -> ## **Guidance for Azure service teams** -> Azure service teams should use the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information, see the [README](./azure/README.md) in the Azure folder. > ## **Guidance for Microsoft Graph service teams** > Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. > --- +# Microsoft REST API Guidelines + This are Microsoft's internal company-wide REST API design guidelines. Teams at Microsoft typically reference this document when setting API design policy. They may additionally create documents specific to their team, adding further guidance or making adjustments as appropriate to their circumstances. From 1a7a6ed94fab8bf9715490b01b4723367a36ba34 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 18 Dec 2023 19:07:42 -0500 Subject: [PATCH 629/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 545bc7e4..7970afaa 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -34,10 +34,10 @@ ensure that Microsoft Graph has a consistent and easy-to-use API surface. A new - Be sustainable and evolvable by using clear API contracts. -The Microsoft Graph REST API Guidelines consist of a concise overview document, a collection of articles on Graph standards, and a library of patterns that provide best practices for resolving common API design problems.Together, these documents serve as the means by which API teams discuss and come to consensus on API review requirements. +The Microsoft Graph REST API Guidelines consist of a concise overview document, a collection of articles on Graph standards, and a library of patterns that provide best practices for resolving common API design problems. Together, these documents serve as the means by which API teams discuss and come to consensus on API review requirements. -Technology and software are constantly changing and evolving, and as such, this -is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface. Consequently, this document will frequently change to add guidance in areas previously uncovered or to clarify existing guidance. It will less frequently change the directional guidance it has already provided. To suggest a change or propose a new idea, +Technology and software are constantly changing and evolving, and as such, this document +is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface. Consequently, this document will frequently change to add guidance in areas previously uncovered or to clarify existing guidance. It will less frequently change the directional guidance it provides. To suggest a change or propose a new idea, [open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose). ### Legend @@ -78,19 +78,19 @@ In general, API design includes the following steps: 1. Specify errors. -When creating your API contract, you define resources based on the domain model supporting your service and identify interactions based on user scenarios. Good API design goes beyond modeling the current state of resources. It is important to plan ahead how the API evolves; to do this, it is essential to understand and document your user scenarios as the foundation of the API design. There is no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. +When creating your API contract, you define resources based on the domain model supporting your service and identify interactions based on user scenarios. Good API design goes beyond modeling the current state of resources. It's important to plan ahead how the API evolves; to do this, it's essential to understand and document your user scenarios as the foundation of the API design. There's no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. We recommend creating a simple resource diagram like the following to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. ![Resource model example](ModelExample.png) -After you define your resources, it’s time to think about the behavior of your API, which can be expressed via HTTP methods and operational resources such as functions and actions. As you think about API behavior, you identify a happy path and various exceptions and deviations that will be expressed as errors and represented by using HTTP codes and error messages. +After you define your resources, it’s time to think about the behavior of your API, which can be expressed via HTTP methods and operational resources such as functions and actions. As you think about API behavior, you identify a happy path and various exceptions and deviations that are expressed as errors and represented by using HTTP codes and error messages. At every step of your design, you need to consider security, privacy, and compliance as intrinsic components of your API implementation. ### Naming -API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API naming guidelines](Guidelines.md#17-naming-guidelines). +API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API naming guidelines](Guidelines-deprecated.md#17-naming-guidelines). Following is a short summary of the most often used conventions. @@ -98,7 +98,7 @@ Following is a short summary of the most often used conventions. | ------------------------------------------------------------------------|-------------------------------| | :no_entry: **MUST NOT** use redundant words in names. | - **Right:** /places/{id}/**displayName** or /phones/{id}/**number**
    - **Wrong:** /places/{id}/**placeName** or /phones/{id}/**phoneNumber** | | :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong:** teamsChat | -| :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong:** msodsUrl or dlp | +| :warning: **SHOULD NOT** use acronyms or abbreviations unless they're broadly understood. | - **Right:** url or htmlSignature
    - **Wrong:** msodsUrl or dlp | | :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong:** addresses | | :heavy_check_mark: **MUST** use plural nouns for collections (for listing type or collection properties). | - **Right:** addresses
    - **Wrong:** address | | :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a *postpositive*).| - **Right:** passersby or mothersInLaw
    - **Wrong:** notaryPublics or motherInLaws | @@ -106,11 +106,11 @@ Following is a short summary of the most often used conventions. | :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces. | - **Right:** automaticRepliesStatus
    - **Wrong:** kebab-case or snake_case | | :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong:** iOLimit or totalIoAmount | | :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong:** webHTML | -| :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geocoordinate, or crosswalk
    - **Wrong:** metaData, semiCircle, or airPlane | +| :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geo coordinate, or crosswalk
    - **Wrong:** metaData, semiCircle, or airPlane | | :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw, or postOffice
    - **Wrong:** paperclip or fullmoon | | **PREFIXES AND SUFFIXES** | | -| :heavy_check_mark: **MUST** suffix date and time properties with Date, Time, or DateTime | - **Right:** dueDate — an Edm.Date
    - **Right:** recurringMeetingTime — an Edm.TimeOfDay
    - **Right:** createdDateTime — an Edm.DateTimeOffset
    - **Wrong:** dueOn or startTime
    - **Right:** Instead, both of the preceding are an Edm.DateTimeOffset | -| :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an `int`, append the units. | - **Right:** passwordValidityPeriod — an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (use of Edm.Duration type is preferable)
    - **Wrong:** passwordValidityPeriod — an Edm.Int32 | +| :heavy_check_mark: **MUST** suffix date and time properties with Date, Time, or DateTime | - **Right:** dueDate—an Edm.Date
    - **Right:** recurringMeetingTime—an Edm.TimeOfDay
    - **Right:** createdDateTime—an Edm.DateTimeOffset
    - **Wrong:** dueOn or startTime
    - **Right:** Instead, both of the preceding are an Edm.DateTimeOffset | +| :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an `int`, append the units. | - **Right:** passwordValidityPeriod—an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (use of Edm.Duration type is preferable)
    - **Wrong:** passwordValidityPeriod — an Edm.Int32 | | :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong:** enabledBool | | :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem or userId on auditActor
    - **Wrong:** webUrl on contact when it's the companyWebUrl | | :ballot_box_with_check: **SHOULD** prefix Boolean properties with `is`, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong:** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom (subjectively more natural than the following examples)
    - **Wrong:** isNewTimeProposalsAllowed or isInvitesFromAllowed (subjectively more awkward that the preceding examples) | @@ -121,7 +121,7 @@ Following is a short summary of the most often used conventions. A Uniform Resource Locator (URL) is how developers access the resources of your API. -Navigation paths to Microsoft Graph resources are generally broken into multiple segments, +Navigation paths to Microsoft Graph resources are broken into multiple segments, `{scheme}://{host}/{version}/{category}/[{pathSegment}][?{query}]` where: - `scheme` and `host` segments are always [`https://graph.microsoft.com`](https://graph.microsoft.com/v1.0/users). @@ -152,9 +152,9 @@ Effectively, top-level categories define a perimeter for the API surface; thus, ### Resource modeling patterns -You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type does not. In Microsoft Graph, this key property is called `id` for server-created key values. If there is a natural name for the key property, then the workload can use that. +You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type doesn't. In Microsoft Graph, this key property is called `id` for server-created key values. If there's a natural name for the key property, then the workload can use that. -Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must use entity types to model addressable resources such as individually addressable items within a collection. For more information, see the [Microsoft REST API Guidelines collection URL patterns](Guidelines-deprecated.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. +Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must use entity types to model addressable resources such as individually addressable items within a collection. For more information, see the [Collection URL patterns](Guidelines-deprecated.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. ```xml @@ -213,11 +213,11 @@ Following are a few pros and cons to decide which pattern to use: - **hierarchy** and **facets** (to a slightly lesser degree) are well-suited for strongly typed client programming languages, whereas **flat bag** is more familiar to developers of less strongly typed languages. -- **facets** has the potential to model what is typically associated with multiple inheritance. +- **facets** has the potential to model what is typically associated with multiple inheritances. - **facets** and **flat bag** lend to syntactically simpler filter query expression. **hierarchy** is more explicit but requires the cast segments in the filter query. -- **hierarchy** can be refined by annotating the collections with OData derived type constraints; see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Validation.V1.md). This annotation restricts the values to certain sub-trees of an inheritance **hierarchy**. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return objects of a type that are semantically not suitable. +- **hierarchy** can be refined by annotating the collections with OData derived type constraints; see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Validation.V1.md). This annotation restricts the values to certain sub-trees of an inheritance **hierarchy**. It makes it explicit that the collection only contains elements of some of the subtypes and helps to not return objects of a type that are semantically not suitable. > **Note:** > As can be seen in a few of the pros and cons, one of the important aspects discussed here is that the API design goes beyond the syntactical aspects of the API. Therefore, it is important to plan ahead how the API evolves, lay the foundation, and allow users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change.** The different modeling patterns differ in how they express syntax and semantics and how they allow the API to evolve without breaking compatibility. For more information, see [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) later in this article. @@ -230,7 +230,7 @@ See [Nullable properties](./articles/nullable.md) for more details. ### Query support -Microsoft Graph APIs returning collections of resources should support basic query options in conformance with [OData specifications](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody) and [Microsoft REST API Guidelines](Guidelines-deprecated.md#9-collections). +Microsoft Graph APIs returning collections of resources should support basic query options in conformance with [OData specifications](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody) and [Collection Guidance](Guidelines-deprecated.md#9-collections). |Requirements | |----------------------------------------------------------------------------------------------------| @@ -348,7 +348,7 @@ The Microsoft Graph definition of breaking changes is based on the **Non-breaking changes:** -- Adding properties that are nullable or have a default value +- To add properties that are nullable or have a default value - Adding a member after the sentinel member to an evolvable enumeration - Removing, renaming, or changing the type of annotation - Changing the order of properties @@ -373,7 +373,7 @@ The Microsoft Graph definition of breaking changes is based on the As the market and technology evolves, your APIs will require modifications. In this case, you must avoid breaking changes and add new resources and features incrementally. If that is not possible, then you must version elements of your APIs. Microsoft Graph allows versioning of elements, including entities and properties. Versioning involves adding a new, uniquely named version of the element and marking the old version as deprecated. -In some cases, there is a natural new name for the element. In other cases, where the original name is still the most descriptive, the suffix _v2 can be added to the original name to make it unique. The original element is then marked as deprecated by using annotations. +In some cases, there's a natural new name for the element. In other cases, where the original name is still the most descriptive, the suffix _v2 can be added to the original name to make it unique. The original element is then marked as deprecated by using annotations. Microsoft Graph provides two public endpoints to support the API lifecycle: - [API sets on the v1.0 endpoint](https://graph.microsoft.com/v1.0) are in general availability (GA) status. @@ -387,7 +387,7 @@ Detailed requirements for versioning and deprecation are described in the [Depre ## Recommended API design patterns -The guidelines in previous sections are intentionally brief and provide a jump start for Microsoft Graph API developers. More detailed design guidance about REST APIs is published at the [Microsoft REST API Guidelines](Guidelines-deprecated.md). Microsoft Graph-specific patterns are outlined in the following table. +The guidelines in previous sections provide a brief overview and a quick start for Microsoft Graph API developers. For a more detailed dive into a specific topic, you can explore [additional articles](./articles/) or learn more about modeling patterns with the Microsoft Graph patterns listed in the following table. | Pattern | Description | |--------------------------------------------------|----------------------------------------------------------------------------| From 92241e2eeffc6ba29b11f52c4e1fed34731cc76a Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 18 Dec 2023 20:27:26 -0500 Subject: [PATCH 630/729] url updates --- graph/GuidelinesGraph.md | 20 +- graph/articles/collections.md | 340 ++++++++++++++++++++++++++++++++++ graph/articles/naming.md | 88 +++++++++ 3 files changed, 438 insertions(+), 10 deletions(-) create mode 100644 graph/articles/collections.md create mode 100644 graph/articles/naming.md diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 7970afaa..2c371bb4 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -90,7 +90,7 @@ At every step of your design, you need to consider security, privacy, and compli ### Naming -API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API naming guidelines](Guidelines-deprecated.md#17-naming-guidelines). +API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API naming guidelines](./articles/naming.md). Following is a short summary of the most often used conventions. @@ -104,7 +104,7 @@ Following is a short summary of the most often used conventions. | :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a *postpositive*).| - **Right:** passersby or mothersInLaw
    - **Wrong:** notaryPublics or motherInLaws | | **CASING** | | | :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces. | - **Right:** automaticRepliesStatus
    - **Wrong:** kebab-case or snake_case | -| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong:** iOLimit or totalIoAmount | +| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong:** iOLimit or totalIoAmount
    - **Exception:** ID properties similar to driveId or applicationId| | :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong:** webHTML | | :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geo coordinate, or crosswalk
    - **Wrong:** metaData, semiCircle, or airPlane | | :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw, or postOffice
    - **Wrong:** paperclip or fullmoon | @@ -154,7 +154,7 @@ Effectively, top-level categories define a perimeter for the API surface; thus, You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type doesn't. In Microsoft Graph, this key property is called `id` for server-created key values. If there's a natural name for the key property, then the workload can use that. -Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must use entity types to model addressable resources such as individually addressable items within a collection. For more information, see the [Collection URL patterns](Guidelines-deprecated.md#93-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. +Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must use entity types to model addressable resources such as individually addressable items within a collection. For more information, see the [Collection URL patterns](./articles/collections.md#3-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. ```xml @@ -230,7 +230,7 @@ See [Nullable properties](./articles/nullable.md) for more details. ### Query support -Microsoft Graph APIs returning collections of resources should support basic query options in conformance with [OData specifications](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody) and [Collection Guidance](Guidelines-deprecated.md#9-collections). +Microsoft Graph APIs returning collections of resources should support basic query options in conformance with [OData specifications](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody) and [Collection Guidance](./articles/collections.md). |Requirements | |----------------------------------------------------------------------------------------------------| @@ -238,8 +238,8 @@ Microsoft Graph APIs returning collections of resources should support basic que | :ballot_box_with_check: **SHOULD** support `/entityTypeCollection/{id}?$expand=navProp1` option for navigation properties of entities. | | :ballot_box_with_check: **SHOULD** support `$filter` with `eq` and `ne` operations on properties of entity collections. | | :heavy_check_mark: **MUST** support pagination of collections (of entity types or complex types) using a [nextLink](http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_ControlInformationnextLinkodatanextL). | -| :ballot_box_with_check: **MAY** support [server-driven pagination](Guidelines-deprecated.md#981-server-driven-paging) of collections using `$skiptoken`. | -| :ballot_box_with_check: **SHOULD** support [client-driven pagination](Guidelines-deprecated.md#982-client-driven-paging) of collections using `$top` and `$skip`. | +| :ballot_box_with_check: **MAY** support [server-driven pagination](./articles/collections.md#81-server-driven-paging) of collections using `$skiptoken`. | +| :ballot_box_with_check: **SHOULD** support [client-driven pagination](./articles/collections.md#82-client-driven-paging) of collections using `$top` and `$skip`. | | :ballot_box_with_check: **SHOULD** support `$count` for collections. | | :ballot_box_with_check: **SHOULD** support sorting with `$orderby` both ascending and descending on properties of the entities. | @@ -252,7 +252,7 @@ Another way to avoid this is to use JSON batch as described in the [Microsoft Gr The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request/response pattern should be one of the first implementation decisions you make. -If possible, APIs SHOULD use resource-based designs with standard HTTP methods rather than operation resources. Operation resources are either functions or actions. According to [OData standards](http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_The_edm:Function_Element_2), a function represents an operation that returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action might have side effects and might return a result represented as a single entity or collection of any type. +If possible, APIs SHOULD use resource-based designs with standard HTTP methods rather than operation resources. Operation resources are either functions or actions. According to [OData standards](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#_Toc31359009), a function represents an operation that returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action might have side effects and might return a result represented as a single entity or collection of any type. | Microsoft Graph rules for modeling behavior | |------------------------------------------------------------------| @@ -409,8 +409,8 @@ The guidelines in previous sections provide a brief overview and a quick start f ## References -- [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/) -- [OData guidelines](http://www.odata.org/documentation/) -- [RESTful web API design](https://docs.microsoft.com/azure/architecture/best-practices/api-design) - [Microsoft Graph documentation](https://docs.microsoft.com/graph/overview) +- [Microsoft REST API Guidelines-deprecated](Guidelines-deprecated.md) +- [OData guidelines](http://www.odata.org/documentation/) +- [Azure RESTful web API design](https://docs.microsoft.com/azure/architecture/best-practices/api-design) - [Graph Explorer](https://developer.microsoft.com/graph/graph-explorer) diff --git a/graph/articles/collections.md b/graph/articles/collections.md new file mode 100644 index 00000000..21e86371 --- /dev/null +++ b/graph/articles/collections.md @@ -0,0 +1,340 @@ +# Collections +## 1. Item keys +Services MAY support durable identifiers for each item in the collection, and that identifier SHOULD be represented in JSON as "id". These durable identifiers are often used as item keys. + +Collections that support durable identifiers MAY support delta queries. + +## 2. Serialization +Collections are represented in JSON using standard array notation. + +## 3. Collection URL patterns +Collections are located directly under the service root when they are top-level, or as a segment under another resource when scoped to that resource. Collection names usually use plural nouns with no suffixes, such as "Collection" or "List". + +For example: + +```http +GET https://api.contoso.com/v1.0/people +``` + + Whenever possible, services MUST support the "/" pattern. +For example: + +```http +GET https://{serviceRoot}/{collection}/{id} +``` + +Where: +- {serviceRoot} – the combination of host (site URL) + the root path to the service +- {collection} – the name of the collection, unabbreviated, pluralized +- {id} – the value of the unique id property. When using the "/" pattern this MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. + +### 3.1. Nested collections and properties +Collection items MAY contain other collections. +For example, a user collection MAY contain user resources that have multiple addresses: + +```http +GET https://api.contoso.com/v1.0/people/123/addresses +``` + +```json +{ + "value": [ + { "street": "1st Avenue", "city": "Seattle" }, + { "street": "124th Ave NE", "city": "Redmond" } + ] +} +``` + +## 4. Big collections +As data grows, so do collections. +Planning for pagination is important for all services. +Therefore, when multiple pages are available, the serialization payload MUST contain the opaque URL for the next page as appropriate. +Refer to the paging guidance for more details. + +Clients MUST be resilient to collection data being either paged or nonpaged for any given request. + +```json +{ + "value":[ + { "id": "Item 1","price": 9 95,"sizes": null}, + { … }, + { … }, + { "id": "Item 99","price": 5 99,"sizes": null} + ], + "@nextLink": "{opaqueUrl}" +} +``` + +## 5. Changing collections +POST requests are not idempotent. +This means that two POST requests sent to a collection resource with exactly the same payload MAY lead to multiple items being created in that collection. +This is often the case for insert operations on items with a server-side generated id. + +For example, the following request: + +```http +POST https://api.contoso.com/v1.0/people +``` + +Would lead to a response indicating the location of the new collection item: + +```http +201 Created +Location: https://api.contoso.com/v1.0/people/123 +``` + +And once executed again, would likely lead to another resource: + +```http +201 Created +Location: https://api.contoso.com/v1.0/people/124 +``` + +While a PUT request would require the indication of the collection item with the corresponding key instead: + +```http +PUT https://api.contoso.com/v1.0/people/123 +``` + +## 6. Sorting collections +The results of a collection query MAY be sorted based on property values. +The property is determined by the value of the _$orderBy_ query parameter. + +The value of the _$orderBy_ parameter contains a comma-separated list of expressions used to sort the items. +A special case of such an expression is a property path terminating on a primitive property. + +The expression MAY include the suffix "asc" for ascending or "desc" for descending, separated from the property name by one or more spaces. +If "asc" or "desc" is not specified, the service MUST order by the specified property in ascending order. + +NULL values MUST sort as "less than" non-NULL values. + +Items MUST be sorted by the result values of the first expression, and then items with the same value for the first expression are sorted by the result value of the second expression, and so on. +The sort order is the inherent order for the type of the property. + +For example: + +```http +GET https://api.contoso.com/v1.0/people?$orderBy=name +``` + +Will return all people sorted by name in ascending order. + +For example: + +```http +GET https://api.contoso.com/v1.0/people?$orderBy=name desc +``` + +Will return all people sorted by name in descending order. + +Sub-sorts can be specified by a comma-separated list of property names with OPTIONAL direction qualifier. + +For example: + +```http +GET https://api.contoso.com/v1.0/people?$orderBy=name desc,hireDate +``` + +Will return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. + +Sorting MUST compose with filtering such that: + +```http +GET https://api.contoso.com/v1.0/people?$filter=name eq 'david'&$orderBy=hireDate +``` + +Will return all people whose name is David sorted in ascending order by hireDate. + +### 6.1. Interpreting a sorting expression +Sorting parameters MUST be consistent across pages, as both client and server-side paging is fully compatible with sorting. + +If a service does not support sorting by a property named in a _$orderBy_ expression, the service MUST respond with an error message as defined in the Responding to Unsupported Requests section. + +## 7. Filtering +The _$filter_ querystring parameter allows clients to filter a collection of resources that are addressed by a request URL. +The expression specified with _$filter_ is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. +Resources for which the expression evaluates to false or to null, or which reference properties that are unavailable due to permissions, are omitted from the response. + +Example: return all Products whose Price is less than $10.00 + +```http +GET https://api.contoso.com/v1.0/products?$filter=price lt 10.00 +``` + +The value of the _$filter_ option is a Boolean expression. + +### 7.1. Filter operations +Services that support _$filter_ SHOULD support the following minimal set of operations. + +Operator | Description | Example +-------------------- | --------------------- | ----------------------------------------------------- +Comparison Operators | | +eq | Equal | city eq 'Redmond' +ne | Not equal | city ne 'London' +gt | Greater than | price gt 20 +ge | Greater than or equal | price ge 10 +lt | Less than | price lt 20 +le | Less than or equal | price le 100 +Logical Operators | | +and | Logical and | price le 200 and price gt 3.5 +or | Logical or | price le 3.5 or price gt 200 +not | Logical negation | not price le 3.5 +Grouping Operators | | +( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 + +### 7.2. Operator examples +The following examples illustrate the use and semantics of each of the logical operators. + +Example: all products with a name equal to 'Milk' + +```http +GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' +``` + +Example: all products with a name not equal to 'Milk' + +```http +GET https://api.contoso.com/v1.0/products?$filter=name ne 'Milk' +``` + +Example: all products with the name 'Milk' that also have a price less than 2.55: + +```http +GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' and price lt 2.55 +``` + +Example: all products that either have the name 'Milk' or have a price less than 2.55: + +```http +GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' or price lt 2.55 +``` + +Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: + +```http +GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 +``` + +### 7.3. Operator precedence +Services MUST use the following operator precedence for supported operators when evaluating _$filter_ expressions. +Operators are listed by category in order of precedence from highest to lowest. +Operators in the same category have equal precedence: + +| Group | Operator | Description | +|:----------------|:---------|:----------------------| +| Grouping | ( ) | Precedence grouping | +| Unary | not | Logical Negation | +| Relational | gt | Greater Than | +| | ge | Greater than or Equal | +| | lt | Less Than | +| | le | Less than or Equal | +| Equality | eq | Equal | +| | ne | Not Equal | +| Conditional AND | and | Logical And | +| Conditional OR | or | Logical Or | + +## 8. Pagination +RESTful APIs that return collections MAY return partial sets. +Consumers of these services MUST expect partial result sets and correctly page through to retrieve an entire set. + +There are two forms of pagination that MAY be supported by RESTful APIs. +Server-driven paging mitigates against denial-of-service attacks by forcibly paginating a request over multiple response payloads. +Client-driven paging enables clients to request only the number of resources that it can use at a given time. + +Sorting and Filtering parameters MUST be consistent across pages, because both client- and server-side paging is fully compatible with both filtering and sorting. + +### 8.1. Server-driven paging +Paginated responses MUST indicate a partial result by including a continuation token in the response. +The absence of a continuation token means that no additional pages are available. + +Clients MUST treat the continuation URL as opaque, which means that query options may not be changed while iterating over a set of partial results. + +Example: + +```http +GET http://api.contoso.com/v1.0/people HTTP/1.1 +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + ..., + "value": [...], + "@nextLink": "{opaqueUrl}" +} +``` + +### 8.2. Client-driven paging +Clients MAY use _$top_ and _$skip_ query parameters to specify a number of results to return and an offset into the collection. + +The server SHOULD honor the values specified by the client; however, clients MUST be prepared to handle responses that contain a different page size or contain a continuation token. + +When both _$top_ and _$skip_ are given by a client, the server SHOULD first apply _$skip_ and then _$top_ on the collection. + +Note: If the server can't honor _$top_ and/or _$skip_, the server MUST return an error to the client informing about it instead of just ignoring the query options. +This will avoid the risk of the client making assumptions about the data returned. + +Example: + +```http +GET http://api.contoso.com/v1.0/people?$top=5&$skip=2 HTTP/1.1 +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + ..., + "value": [...] +} +``` + +### 8.3. Additional considerations +**Stable order prerequisite:** Both forms of paging depend on the collection of items having a stable order. +The server MUST supplement any specified order criteria with additional sorts (typically by key) to ensure that items are always ordered consistently. + +**Missing/repeated results:** Even if the server enforces a consistent sort order, results MAY be missing or repeated based on creation or deletion of other resources. +Clients MUST be prepared to deal with these discrepancies. +The server SHOULD always encode the record ID of the last read record, helping the client in the process of managing repeated/missing results. + +**Combining client- and server-driven paging:** Note that client-driven paging does not preclude server-driven paging. +If the page size requested by the client is larger than the default page size supported by the server, the expected response would be the number of results specified by the client, paginated as specified by the server paging settings. + +**Page Size:** Clients MAY request server-driven paging with a specific page size by specifying a _$maxpagesize_ preference. +The server SHOULD honor this preference if the specified page size is smaller than the server's default page size. + +**Paginating embedded collections:** It is possible for both client-driven paging and server-driven paging to be applied to embedded collections. +If a server paginates an embedded collection, it MUST include additional continuation tokens as appropriate. + +**Recordset count:** Developers who want to know the full number of records across all pages, MAY include the query parameter _$count=true_ to tell the server to include the count of items in the response. + +## 9. Compound collection operations +Filtering, Sorting and Pagination operations MAY all be performed against a given collection. +When these operations are performed together, the evaluation order MUST be: + +1. **Filtering**. This includes all range expressions performed as an AND operation. +2. **Sorting**. The potentially filtered list is sorted according to the sort criteria. +3. **Pagination**. The materialized paginated view is presented over the filtered, sorted list. This applies to both server-driven pagination and client-driven pagination. + +## 10. Empty Results + +When a filter is performed on a collection and the result set is empty you MUST respond with a valid response body and a 200 response code. +In this example the filters supplied by the client resulted in a empty result set. +The response body is returned as normal and the _value_ attribute is set to a empty collection. +A client MAY be expecting metadata attributes like _maxItems_ based on the format of your responses to similar calls which produced results. +You SHOULD maintain consistency in your API whenever possible. + +```http +GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + ..., + "maxItems": 0, + "value": [] +} +``` diff --git a/graph/articles/naming.md b/graph/articles/naming.md new file mode 100644 index 00000000..fc202c71 --- /dev/null +++ b/graph/articles/naming.md @@ -0,0 +1,88 @@ +# Naming guidelines +## 1. Approach +Naming policies should aid developers in discovering functionality without having to constantly refer to documentation. +Use of common patterns and standard conventions greatly aids developers in correctly guessing common property names and meanings. +Services SHOULD use verbose naming patterns and SHOULD NOT use abbreviations other than acronyms that are the dominant mode of expression in the domain being represented by the API, (e.g. Url). + +## 2. Casing +- Acronyms SHOULD follow the casing conventions as though they were regular words (e.g. Url). +- All identifiers including namespaces, entityTypes, entitySets, properties, actions, functions and enumeration values SHOULD use lowerCamelCase. +- HTTP headers are the exception and SHOULD use standard HTTP convention of Capitalized-Hyphenated-Terms. + +## 3. Names to avoid +Certain names are so overloaded in API domains that they lose all meaning or clash with other common usages in domains that cannot be avoided when using REST APIs, such as OAUTH. +Services SHOULD NOT use the following names: +- Context +- Scope +- Resource + +## 4. Forming compound names +- Services SHOULD avoid using articles such as 'a', 'the', 'of' unless needed to convey meaning. + - e.g. names such as aUser, theAccount, countOfBooks SHOULD NOT be used, rather user, account, bookCount SHOULD be preferred. +- Services SHOULD add a type to a property name when not doing so would cause ambiguity about how the data is represented or would cause the service not to use a common property name. +- When adding a type to a property name, services MUST add the type at the end, e.g. createdDateTime. + +## 5. Identity properties +- Services MUST use string types for identity properties. +- For OData services, the service MUST use the OData @id property to represent the canonical identifier of the resource. +- Services MAY use the simple 'id' property to represent a local or legacy primary key value for a resource. +- Services SHOULD use the name of the relationship postfixed with 'Id' to represent a foreign key to another resource, e.g. subscriptionId. + - The content of this property SHOULD be the canonical ID of the referenced resource. + +## 6. Date and time properties + +- For properties requiring both date and time, services MUST use the suffix 'DateTime'. +- For properties requiring only date information without specifying time, services MUST use the suffix 'Date', e.g. birthDate. +- For properties requiring only time information without specifying date, services MUST use the suffix 'Time', e.g. appointmentStartTime. + +## 7. Name properties +- For the overall name of a resource typically shown to users, services MUST use the property name 'displayName'. +- Services MAY use other common naming properties, e.g. givenName, surname, signInName. + +## 8. Collections and counts +- Services MUST name collections as plural nouns or plural noun phrases using correct English. +- Services MAY use simplified English for nouns that have plurals not in common verbal usage. + - e.g. schemas MAY be used instead of schemata. +- Services MUST name counts of resources with a noun or noun phrase suffixed with 'Count'. + +## 9. Common property names +Where services have a property, whose data matches the names below, the service MUST use the name from this table. +This table will grow as services add terms that will be more commonly used. +Service owners adding such terms SHOULD propose additions to this document. + +| | +|------------- | + attendees | + body | + createdDateTime | + childCount | + children | + contentUrl | + country | + createdBy | + displayName | + errorUrl | + eTag | + event | + expirationDateTime | + givenName | + jobTitle | + kind | + id | + lastModifiedDateTime | + location | + memberOf | + message | + name | + owner | + people | + person | + postalCode | + photo | + preferredLanguage | + properties | + signInName | + surname | + tags | + userPrincipalName | + webUrl | From a6cc17ccf9dc452b8b15d652eec170456849f52c Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 18 Dec 2023 22:19:55 -0500 Subject: [PATCH 631/729] branding --- graph/GuidelinesGraph.md | 57 ++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 2c371bb4..2f736410 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -15,7 +15,7 @@ Table of contents - [Behavior modeling](#behavior-modeling) - [Error handling](#error-handling) - [External standards](#external-standards) - - [API contract and non-backward compatible changes](#api-contract-and-non-backward-compatible-changes) + - [API contract and nonbackward compatible changes](#api-contract-and-nonbackward-compatible-changes) - [Versioning and deprecation](#versioning-and-deprecation) - [Recommended API design patterns](#recommended-api-design-patterns) - [References](#references) @@ -26,7 +26,7 @@ Table of contents When building a digital ecosystem API usability becomes a business priority. The success of your ecosystem depends on APIs that are easy to discover, simple to use, fit for purpose, and consistent across your products. This document offers guidance that Microsoft Graph API producer teams MUST follow to -ensure that Microsoft Graph has a consistent and easy-to-use API surface. A new API design should meet the following goals: +ensure that Microsoft Graph has a consistent and easy-to-use API surface. A new API design SHOULD meet the following goals: - Be developer friendly by using consistent naming, patterns, and web standards (HTTP, REST, JSON). @@ -36,8 +36,7 @@ ensure that Microsoft Graph has a consistent and easy-to-use API surface. A new The Microsoft Graph REST API Guidelines consist of a concise overview document, a collection of articles on Graph standards, and a library of patterns that provide best practices for resolving common API design problems. Together, these documents serve as the means by which API teams discuss and come to consensus on API review requirements. -Technology and software are constantly changing and evolving, and as such, this document -is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface. Consequently, this document will frequently change to add guidance in areas previously uncovered or to clarify existing guidance. It will less frequently change the directional guidance it provides. To suggest a change or propose a new idea, +Technology and software are constantly changing and evolving, and as such, this document is intended to be a living document. API guidelines that change frequently lead to an uneven and inconsistent API surface.Therefore, the general principles and directions that this document offers will be more stable than the specific recommendations for areas that are new or significantly different. The guidance might change as needed to address new scenarios and to clarify existing guidance. The guidance might change as needed to address new scenarios and to clarify existing guidance. To suggest a change or propose a new idea, [open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose). ### Legend @@ -56,7 +55,7 @@ If you do not follow this advice, you MUST disclose your reasons during the Micr ## Design approach -The design of your API is arguably the most important investment you will make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote an API-first design approach where you begin your product design by focusing on how information is exchanged and represented and by creating an interface contract for your API, which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface from your implementation and is essential for agility, predictability, and reuse of your APIs. +The design of your API is arguably the most important investment you'll make. API design is what creates the first impression for developers when they discover and learn how to use your APIs. We promote an API-first design approach where you begin your product design by focusing on how information is exchanged and represented and by creating an interface contract for your API, which is followed by design and implementation of the backing service. This approach ensures decoupling of the interface from your implementation and is essential for agility, predictability, and reuse of your APIs. An established interface contract allows developers to use your API while internal teams are still working on implementation; API specifications enable designing user experience and test cases in parallel. Starting with user-facing contracts also promotes a good understanding of system interactions, your modeling domain, and an understanding of how the service will evolve. @@ -78,7 +77,7 @@ In general, API design includes the following steps: 1. Specify errors. -When creating your API contract, you define resources based on the domain model supporting your service and identify interactions based on user scenarios. Good API design goes beyond modeling the current state of resources. It's important to plan ahead how the API evolves; to do this, it's essential to understand and document your user scenarios as the foundation of the API design. There's no one-to-one correspondence between domain model elements and API resources because you should simplify your customer facing APIs for better usability and to obfuscate implementation details. +When creating your API contract, you define resources based on the domain model supporting your service and identify interactions based on user scenarios. Good API design goes beyond modeling the current state of resources. It's important to plan ahead how the API evolves; to do this, it's essential to understand and document your user scenarios as the foundation of the API design. There's no one-to-one correspondence between domain model elements and API resources because you SHOULD simplify your customer facing APIs for better usability and to obfuscate implementation details. We recommend creating a simple resource diagram like the following to show resources and their relationships and make it easier to reason about modeling choices and the shape of your API. @@ -90,7 +89,7 @@ At every step of your design, you need to consider security, privacy, and compli ### Naming -API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and must be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you should favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Microsoft REST API naming guidelines](./articles/naming.md). +API resources are typically described by nouns. Resource and property names appear in API URLs and payloads and MUST be descriptive and easy to understand. Ease of understanding comes from familiarity and recognition; therefore, when thinking about naming, you SHOULD favor consistency with other Microsoft Graph APIs, names in the product user interface, and industry standards. Microsoft Graph naming conventions follow the [Naming guidelines](./articles/naming.md). Following is a short summary of the most often used conventions. @@ -132,7 +131,7 @@ Navigation paths to Microsoft Graph resources are broken into multiple segments, - `pathSegment` is one or many navigation segments that can address an entity, collection of entities, property, or operation available for an entity. -- `query` string must follow the OData standard for query representations and is covered in the Query section of OData specifications. +- `query` string MUST follow the OData standard for query representations and is covered in the Query section of OData specifications. While HTTP defines no constraints on how different resources are related, it does encourage the use of URL path segment hierarchies to convey relationships. In Microsoft Graph, relationships between resources are supported by the OData concepts of singletons, entity sets, entities, complex types, and navigation properties. @@ -154,7 +153,7 @@ Effectively, top-level categories define a perimeter for the API surface; thus, You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type doesn't. In Microsoft Graph, this key property is called `id` for server-created key values. If there's a natural name for the key property, then the workload can use that. -Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you must use entity types to model addressable resources such as individually addressable items within a collection. For more information, see the [Collection URL patterns](./articles/collections.md#3-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. +Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you SHOULD use entity types to model addressable resources such as individually addressable items within a collection. For more information, see the [Collection URL patterns](./articles/collections.md#3-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. ```xml @@ -230,7 +229,7 @@ See [Nullable properties](./articles/nullable.md) for more details. ### Query support -Microsoft Graph APIs returning collections of resources should support basic query options in conformance with [OData specifications](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody) and [Collection Guidance](./articles/collections.md). +Microsoft Graph APIs returning collections of resources SHOULD support basic query options in conformance with [OData specifications](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody) and [Collection Guidance](./articles/collections.md). |Requirements | |----------------------------------------------------------------------------------------------------| @@ -243,14 +242,14 @@ Microsoft Graph APIs returning collections of resources should support basic que | :ballot_box_with_check: **SHOULD** support `$count` for collections. | | :ballot_box_with_check: **SHOULD** support sorting with `$orderby` both ascending and descending on properties of the entities. | -The query options part of an OData URL can be quite long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or processing the request. One way to avoid this is to use the POST verb instead of GET with the `$query` segment, and pass the query options part of the URL in the request body as described in the chapter +The query options part of an OData URL can be long, potentially exceeding the maximum length of URLs supported by components involved in transmitting or processing the request. One way to avoid this is to use the POST verb instead of GET with the `$query` segment, and pass the query options part of the URL in the request body as described in the chapter [OData Query Options](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody). Another way to avoid this is to use JSON batch as described in the [Microsoft Graph batching documentation](https://docs.microsoft.com/graph/json-batching#bypassing-url-length-limitations-with-batching). ### Behavior modeling -The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request/response pattern should be one of the first implementation decisions you make. +The HTTP operations dictate how your API behaves. The URL of an API, along with its request/response bodies, establishes the overall contract that developers have with your service. As an API provider, how you manage the overall request/response pattern SHOULD be one of the first implementation decisions you make. If possible, APIs SHOULD use resource-based designs with standard HTTP methods rather than operation resources. Operation resources are either functions or actions. According to [OData standards](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#_Toc31359009), a function represents an operation that returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action might have side effects and might return a result represented as a single entity or collection of any type. @@ -264,14 +263,14 @@ If possible, APIs SHOULD use resource-based designs with standard HTTP methods r | :warning: **SHOULD NOT** use PUT for updating resources. | | :ballot_box_with_check: **SHOULD** avoid using multiple round trips to complete a single logical operation. | -Operation resources must have a binding parameter that matches the type of the bound resource. In addition, both actions and functions support overloading, meaning an API definition might contain multiple actions or functions with the same name. +Operation resources MUST have a binding parameter that matches the type of the bound resource. In addition, both actions and functions support overloading, meaning an API definition might contain multiple actions or functions with the same name. -For a complete list of standard HTTP operations, see the [Microsoft REST API Guidelines error condition responses](Guidelines-deprecated.md#7102-error-condition-responses). +For a complete list of standard HTTP operations, see the [Error condition responses](Guidelines-deprecated.md#7102-error-condition-responses). ### Error handling -Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs should follow when returning error condition responses. You can improve API traceability and consistency by using the recommended Microsoft Graph error model and the Microsoft Graph utilities library to provide a standard implementation for your service: +Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs SHOULD follow when returning error condition responses. You can improve API traceability and consistency by using the recommended Microsoft Graph error model and the Microsoft Graph utilities library to provide a standard implementation for your service: ```http { @@ -287,7 +286,7 @@ Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs shoul } ``` -The top-level error code must match the HTTP response status code description, converted to camelCase, as listed in the [Status Code Registry (iana.org)](https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml). The following examples demonstrate error modeling for common use cases: +The top-level error code MUST match the HTTP response status code description, converted to camelCase, as listed in the [Status Code Registry (iana.org)](https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml). The following examples demonstrate error modeling for common use cases: - **Simple error**: An API wants to report an error with top-level details only. The error object contains the top-level error code, message and target (optional). @@ -330,21 +329,21 @@ For a complete mapping of error codes to HTTP statuses, see ## External standards -For ease of client use and interoperatibility, some APIs should implement a standard that is defined external to Microsoft Graph and OData. -Workloads should follow these standards exactly, even if they conflict with the OData standard and/or the Microsoft Graph guidelines. -Workloads must define these standards in their CSDL model if they do not conflict with the OData standard. -Standards that *do* conflict with the OData standard may be defined in the CSDL in one of two ways: +For ease of client use and interoperatibility, some APIs might implement a standard that is defined external to Microsoft Graph and OData. +Workloads SHOULD follow these standards exactly, even if they conflict with the OData standard and/or the Microsoft Graph guidelines. +Workloads SHOULD define these standards in their CSDL model if they do not conflict with the OData standard. +Standards that *do* conflict with the OData standard might be defined in the CSDL in one of two ways: 1. Using `Edm.Untyped` only and support for the external standard will come directly from the service implementation; OR -2. Adding CSDL elements to model the external standard using `Edm.String` for `EnumType`s that conflict with the OData standard and `Edm.Untyped` wherever any other conflict with the OData standard occurs +2. Adding CSDL elements to model the external standard using `Edm.String` for `EnumType`s that conflict with the OData standard and `Edm.Untyped` wherever any other conflict with the OData standard occurs. -In either case, any use of `Edm.String` instead of an `EnumType` or any use of `Edm.Untyped` must provide a [description annotation](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.xml#L105) to document references to the standard that the client is expected to follow. +In either case, any use of `Edm.String` instead of an `EnumType` or any use of `Edm.Untyped` MUST provide a [description annotation](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.xml#L105) to document references to the standard that the client is expected to follow. The benefit of the second approach is that strongly-typed models have SDK support for clients and also have significant tooling support for both the workload and clients. -Note that it is backwards compatible for a workload to migrate from the second approach to the first approach in case the external standard is *initially* compliant with the OData standard and *later* conflicts with the OData standard. +Note that it's backwards compatible for a workload to migrate from the second approach to the first approach in case the external standard is *initially* compliant with the OData standard and *later* conflicts with the OData standard. -## API contract and non-backward compatible changes +## API contract and nonbackward compatible changes -The Microsoft Graph definition of breaking changes is based on the -[Microsoft REST API Guidelines definition of a breaking change](Guidelines-deprecated.md#123-definition-of-a-breaking-change). In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and is not considered a breaking change. +The Microsoft Graph defines breaking changes as any change that requires a client to change its implementation to continue working with the service, it includes changes to the API contract, API behavior, and nonbackward compatible changes. +In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and is not considered a breaking change. **Non-breaking changes:** @@ -361,7 +360,7 @@ The Microsoft Graph definition of breaking changes is based on the - Removing, renaming, or changing an incompatible type of a declared property - Removing or renaming APIs or API parameters - Adding a required request header -- Adding EnumType members for non-evolvable enumerations +- Adding EnumType members for nonevolvable enumerations - Adding Nullable="false" properties to existing types - Adding a parameter not marked as [Nullable](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_Nullable) to existing actions - Adding a parameter not marked as [Optional](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md#OptionalParameter) to an existing function @@ -369,9 +368,11 @@ The Microsoft Graph definition of breaking changes is based on the - Introducing server-side pagination to existing collections - Making significant changes to the performance of APIs such as increased latency, rate limits, or concurrency +The applicable changes described in the [Model Versioning of the OData V4.01 spec](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_ModelVersioning) SHOULD be considered part of the minimum bar that all services MUST consider a breaking change. + ### Versioning and deprecation -As the market and technology evolves, your APIs will require modifications. In this case, you must avoid breaking changes and add new resources and features incrementally. If that is not possible, then you must version elements of your APIs. Microsoft Graph allows versioning of elements, including entities and properties. Versioning involves adding a new, uniquely named version of the element and marking the old version as deprecated. +As the market and technology evolves, your APIs will require modifications. In this case, you MUST avoid breaking changes and add new resources and features incrementally. If that isn't possible, then you MUST version elements of your APIs. Microsoft Graph allows versioning of elements, including entities and properties. Versioning involves adding a new, uniquely named version of the element and marking the old version as deprecated. In some cases, there's a natural new name for the element. In other cases, where the original name is still the most descriptive, the suffix _v2 can be added to the original name to make it unique. The original element is then marked as deprecated by using annotations. From 3ff1be6d4333c11e5436596118f1240d3f62c667 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 18 Dec 2023 22:30:01 -0500 Subject: [PATCH 632/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 2f736410..ec7fc4f0 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -153,7 +153,7 @@ Effectively, top-level categories define a perimeter for the API surface; thus, You can model structured resources for your APIs by using the OData entity type or complex type. The main difference between these types is that an entity type declares a key property to uniquely identify its objects, and a complex type doesn't. In Microsoft Graph, this key property is called `id` for server-created key values. If there's a natural name for the key property, then the workload can use that. -Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you SHOULD use entity types to model addressable resources such as individually addressable items within a collection. For more information, see the [Collection URL patterns](./articles/collections.md#3-collection-url-patterns). Complex types are better suited to represent composite properties of API entities. +Because objects of complex types in Microsoft Graph don’t have unique identifiers, they are not directly addressable via URIs. Therefore, you SHOULD use entity types to model addressable resources such as individually addressable items within a collection. For more information, see the [Collection guidance](./articles/collections.md). Complex types are better suited to represent composite properties of API entities. ```xml @@ -229,7 +229,7 @@ See [Nullable properties](./articles/nullable.md) for more details. ### Query support -Microsoft Graph APIs returning collections of resources SHOULD support basic query options in conformance with [OData specifications](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody) and [Collection Guidance](./articles/collections.md). +Microsoft Graph APIs returning collections of resources SHOULD support basic query options in conformance with [OData specifications](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_PassingQueryOptionsintheRequestBody) and [Collection guidance](./articles/collections.md). |Requirements | |----------------------------------------------------------------------------------------------------| From 7b1557f502fe3c8b3321711ebe53e5fe72a66696 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 18 Dec 2023 22:38:49 -0500 Subject: [PATCH 633/729] casing --- graph/GuidelinesGraph.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index ec7fc4f0..52567e6f 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -103,7 +103,7 @@ Following is a short summary of the most often used conventions. | :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a *postpositive*).| - **Right:** passersby or mothersInLaw
    - **Wrong:** notaryPublics or motherInLaws | | **CASING** | | | :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces. | - **Right:** automaticRepliesStatus
    - **Wrong:** kebab-case or snake_case | -| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Wrong:** iOLimit or totalIoAmount
    - **Exception:** ID properties similar to driveId or applicationId| +| :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Right:** 'id' properties similar to driveId or applicationId
    - **Wrong:** iOLimit or totalIoAmount| | :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong:** webHTML | | :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geo coordinate, or crosswalk
    - **Wrong:** metaData, semiCircle, or airPlane | | :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw, or postOffice
    - **Wrong:** paperclip or fullmoon | @@ -212,7 +212,7 @@ Following are a few pros and cons to decide which pattern to use: - **hierarchy** and **facets** (to a slightly lesser degree) are well-suited for strongly typed client programming languages, whereas **flat bag** is more familiar to developers of less strongly typed languages. -- **facets** has the potential to model what is typically associated with multiple inheritances. +- **facets** has the potential to model what is typically associated with multiple inheritances. - **facets** and **flat bag** lend to syntactically simpler filter query expression. **hierarchy** is more explicit but requires the cast segments in the filter query. From 27ee3eafbd54f7aca26688947689d653c1dd5bb5 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 18 Dec 2023 23:14:55 -0500 Subject: [PATCH 634/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 52567e6f..14fa3071 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -265,12 +265,11 @@ If possible, APIs SHOULD use resource-based designs with standard HTTP methods r Operation resources MUST have a binding parameter that matches the type of the bound resource. In addition, both actions and functions support overloading, meaning an API definition might contain multiple actions or functions with the same name. -For a complete list of standard HTTP operations, see the [Error condition responses](Guidelines-deprecated.md#7102-error-condition-responses). - +For an additional list of standard HTTP methods, see the [RFC7230](https://www.rfc-editor.org/rfc/rfc9112)). ### Error handling -Microsoft REST API Guidelines provide guidelines that Microsoft Graph APIs SHOULD follow when returning error condition responses. You can improve API traceability and consistency by using the recommended Microsoft Graph error model and the Microsoft Graph utilities library to provide a standard implementation for your service: + You can improve API traceability and consistency by using the recommended Microsoft Graph error model and the Microsoft Graph utilities library to provide a standard implementation for your service: ```http { @@ -295,7 +294,7 @@ The top-level error code MUST match the HTTP response status code description, c "error": { "code": "badRequest", "message": "Cannot process the request because it is malformed or incorrect.", - "target": "resource X (Optional)" + "target": "resource X (Optional)" } } ``` @@ -322,6 +321,8 @@ The top-level error code MUST match the HTTP response status code description, c | :heavy_check_mark: **MUST** return a 429 Too Many Requests error when the client exceeded throttling limits, and a 503 Service Unavailable error when the service overloaded but the client is within throttling limits.| | :ballot_box_with_check: **SHOULD** return a 404 Not Found error if a 403 error would result in information disclosure. | +For additional guidance, see [Error condition responses article](Guidelines-deprecated.md#7102-error-condition-responses). + For a complete mapping of error codes to HTTP statuses, see [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). From 49923da8df6a9431780bfa4bce65b8999dc8458f Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Mon, 18 Dec 2023 23:18:41 -0500 Subject: [PATCH 635/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 14fa3071..704106a5 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -389,7 +389,7 @@ Detailed requirements for versioning and deprecation are described in the [Depre ## Recommended API design patterns -The guidelines in previous sections provide a brief overview and a quick start for Microsoft Graph API developers. For a more detailed dive into a specific topic, you can explore [additional articles](./articles/) or learn more about modeling patterns with the Microsoft Graph patterns listed in the following table. +The guidelines in previous sections provide a brief overview and a quick start for Microsoft Graph API developers. For a more detailed dive into a specific topic, you can explore [additional articles](./articles/) or learn more about [modeling patterns with the Microsoft Graph](./patterns/) listed in the following table. | Pattern | Description | |--------------------------------------------------|----------------------------------------------------------------------------| From bfb1a8b60daa986fed36ea044b9da2ca84e24bb7 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 20 Dec 2023 19:23:04 -0500 Subject: [PATCH 636/729] error article --- graph/Guidelines-deprecated.md | 2 +- graph/GuidelinesGraph.md | 6 +- graph/articles/errorResponses.md | 133 +++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 graph/articles/errorResponses.md diff --git a/graph/Guidelines-deprecated.md b/graph/Guidelines-deprecated.md index 1264cfcb..b22e5339 100644 --- a/graph/Guidelines-deprecated.md +++ b/graph/Guidelines-deprecated.md @@ -1,6 +1,6 @@ > # DEPRECATION NOTICE TO READERS -> This document has been deprecated and has been merged with [Microsoft Graph REST API Guidelines](./graph/Guidelines_deprecated.md). Please refer to the notes below for the latest guidance. +> This document is being deprecated and merged with the [Microsoft Graph REST API Guidelines](./graph/Guidelines_deprecated.md)guidelines, with a removal date of 07/01/2024.. Please refer to the notes below for the latest guidance. > > ## **Guidance for Microsoft Graph service teams** > Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 704106a5..648799ee 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -269,7 +269,7 @@ For an additional list of standard HTTP methods, see the [RFC7230](https://www.r ### Error handling - You can improve API traceability and consistency by using the recommended Microsoft Graph error model and the Microsoft Graph utilities library to provide a standard implementation for your service: + To improve API traceability and consistency you MUST use the recommended Microsoft Graph error model and the Microsoft Graph utilities library to provide a standard implementation for your service: ```http { @@ -294,7 +294,7 @@ The top-level error code MUST match the HTTP response status code description, c "error": { "code": "badRequest", "message": "Cannot process the request because it is malformed or incorrect.", - "target": "resource X (Optional)" + "target": "resource" } } ``` @@ -321,7 +321,7 @@ The top-level error code MUST match the HTTP response status code description, c | :heavy_check_mark: **MUST** return a 429 Too Many Requests error when the client exceeded throttling limits, and a 503 Service Unavailable error when the service overloaded but the client is within throttling limits.| | :ballot_box_with_check: **SHOULD** return a 404 Not Found error if a 403 error would result in information disclosure. | -For additional guidance, see [Error condition responses article](Guidelines-deprecated.md#7102-error-condition-responses). +For additional guidance, see [Error condition responses article](./articles/errorResponses.md). For a complete mapping of error codes to HTTP statuses, see [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). diff --git a/graph/articles/errorResponses.md b/graph/articles/errorResponses.md new file mode 100644 index 00000000..13d1b99f --- /dev/null +++ b/graph/articles/errorResponses.md @@ -0,0 +1,133 @@ +# Error condition responses + +For non-success conditions, developers SHOULD be able to write one piece of code that handles errors consistently across different Microsoft REST API services. +This allows building of simple and reliable infrastructure to handle exceptions as a separate flow from successful responses. +The following is based on the OData v4 JSON spec. +However, it is very generic and does not require specific OData constructs. +APIs SHOULD use this format even if they are not using other OData constructs. + +The error response MUST be a single JSON object. +This object MUST have a name/value pair named "error". The value MUST be a JSON object. + +This object MUST contain name/value pairs with the names "code" and "message", and it MAY contain name/value pairs with the names "target", "details" and "innererror." + +The value for the "code" name/value pair is a language-independent string and MUST match the HTTP response status code description, converted to camelCase, as listed in the [Status Code Registry (iana.org)](https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml) +For example, if the HTTP status code is "Not Found", then the "code" value MUST be "notFound". + +Most services will require a larger number of more specific error codes, which are not interesting to all clients. +These error codes SHOULD be exposed in the "innererror" name/value pair as described below. +Introducing a new value for "code" that is visible to existing clients is a breaking change and requires a version increase. +Services can avoid breaking changes by adding new error codes to "innererror" instead. + +The value for the "message" name/value pair MUST be a human-readable representation of the error. +It is intended as an aid to developers and is not suitable for exposure to end users. +Services wanting to expose a suitable message for end users MUST do so through an [annotation][odata-json-annotations] or custom property. +Services SHOULD NOT localize "message" for the end user, because doing so might make the value unreadable to the app developer who may be logging the value, as well as make the value less searchable on the Internet. + +The value for the "target" name/value pair is the target of the particular error (e.g., the name of the property in error). + +The value for the "details" name/value pair MUST be an array of JSON objects that MUST contain name/value pairs for "code" and "message", and MAY contain a name/value pair for "target", as described above. +The objects in the "details" array usually represent distinct, related errors that occurred during the request. +See example below. + +The value for the "innererror" name/value pair MUST be an object. +The contents of this object are service-defined. +Services wanting to return more specific errors than the root-level code MUST do so by including a name/value pair for "code" and a nested "innererror". Each nested "innererror" object represents a higher level of detail than its parent. +When evaluating errors, clients MUST traverse through all of the nested "innererrors" and choose the deepest one that they understand. +This scheme allows services to introduce new error codes anywhere in the hierarchy without breaking backwards compatibility, so long as old error codes still appear. +The service MAY return different levels of depth and detail to different callers. +For example, in development environments, the deepest "innererror" MAY contain internal information that can help debug the service. +To guard against potential security concerns around information disclosure, services SHOULD take care not to expose too much detail unintentionally. +Error objects MAY also include custom server-defined name/value pairs that MAY be specific to the code. +Error types with custom server-defined properties SHOULD be declared in the service's metadata document. +See example below. + +Error responses MAY contain [annotations][odata-json-annotations] in any of their JSON objects. + +We recommend that for any transient errors that may be retried, services SHOULD include a Retry-After HTTP header indicating the minimum number of seconds that clients SHOULD wait before attempting the operation again. + +##### ErrorResponse : Object + +Property | Type | Required | Description +-------- | ---- | -------- | ----------- +`error` | Error | ✔ | The error object. + +##### Error : Object + +Property | Type | Required | Description +-------- | ---- | -------- | ----------- +`code` | String | ✔ | One of a server-defined set of error codes. +`message` | String | ✔ | A human-readable representation of the error. +`target` | String | | The target of the error. +`details` | Error[] | | An array of details about specific errors that led to this reported error. +`innererror` | InnerError | | An object containing more specific information than the current object about the error. + +##### InnerError : Object + +Property | Type | Required | Description +-------- | ---- | -------- | ----------- +`code` | String | | A more specific error code than was provided by the containing error. +`innererror` | InnerError | | An object containing more specific information than the current object about the error. + +##### Examples + +Example of "innererror": + +```json +{ + "error": { + "code": "unauthorized", + "message": "Previous passwords may not be reused", + "target": "password", + "innererror": { + "code": "passwordError", + "innererror": { + "code": "passwordDoesNotMeetPolicy", + "minLength": "6", + "maxLength": "64", + "characterTypes": ["lowerCase","upperCase","number","symbol"], + "minDistinctCharacterTypes": "2", + "innererror": { + "code": "passwordReuseNotAllowed" + } + } + } + } +} +``` + +In this example, the most basic error code is "unauthorized", but for clients that are interested, there are more specific error codes in "innererror." +The "passwordReuseNotAllowed" code may have been added by the service at a later date, having previously only returned "passwordDoesNotMeetPolicy." +Existing clients do not break when the new error code is added, but new clients MAY take advantage of it. +The "passwordDoesNotMeetPolicy" error also includes additional name/value pairs that allow the client to determine the server's configuration, validate the user's input programmatically, or present the server's constraints to the user within the client's own localized messaging. + +Example of "details": + +```json +{ + "error": { + "code": "badRequest", + "message": "Multiple errors in ContactInfo data", + "target": "contactInfo", + "details": [ + { + "code": "nullValue", + "target": "phoneNumber", + "message": "Phone number must not be null" + }, + { + "code": "nullValue", + "target": "lastName", + "message": "Last name must not be null" + }, + { + "code": "malformedValue", + "target": "address", + "message": "Address is not valid" + } + ] + } +} +``` + +In this example there were multiple problems with the request, with each individual error listed in "details." From 568d6c021827f345cc706add8482f9a9f06981b5 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 21 Dec 2023 11:43:19 -0500 Subject: [PATCH 637/729] updated naming article --- graph/articles/naming.md | 6 ++--- graph/articles/nullable.md | 28 +++++++++++------------ graph/patterns/long-running-operations.md | 4 ---- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/graph/articles/naming.md b/graph/articles/naming.md index fc202c71..70caef29 100644 --- a/graph/articles/naming.md +++ b/graph/articles/naming.md @@ -1,12 +1,12 @@ -# Naming guidelines +# Naming ## 1. Approach Naming policies should aid developers in discovering functionality without having to constantly refer to documentation. Use of common patterns and standard conventions greatly aids developers in correctly guessing common property names and meanings. -Services SHOULD use verbose naming patterns and SHOULD NOT use abbreviations other than acronyms that are the dominant mode of expression in the domain being represented by the API, (e.g. Url). +Services SHOULD use verbose naming patterns and MUST NOT use abbreviations other than acronyms that are the dominant mode of expression in the domain being represented by the API, (e.g. Url). ## 2. Casing - Acronyms SHOULD follow the casing conventions as though they were regular words (e.g. Url). -- All identifiers including namespaces, entityTypes, entitySets, properties, actions, functions and enumeration values SHOULD use lowerCamelCase. +- All identifiers including namespaces, entityTypes, entitySets, properties, actions, functions and enumeration values MUST use lowerCamelCase. - HTTP headers are the exception and SHOULD use standard HTTP convention of Capitalized-Hyphenated-Terms. ## 3. Names to avoid diff --git a/graph/articles/nullable.md b/graph/articles/nullable.md index 7e581620..f9ffcc20 100644 --- a/graph/articles/nullable.md +++ b/graph/articles/nullable.md @@ -26,7 +26,7 @@ Below are some examples of nullable and non-nullable properties. ## HTTP Requests -### {1} Create a servicePrincipal with no properties +### 1. Create a servicePrincipal with no properties ```HTTP POST /servicePrincipals @@ -40,7 +40,7 @@ POST /servicePrincipals } ``` -### {2} Create a servicePrincipal without a display name +### 2. Create a servicePrincipal without a display name ```HTTP POST /servicePrincipals @@ -62,7 +62,7 @@ Notes: 2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL 3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL -### {3} Update the display name of a service principal to null +### 3. Update the display name of a service principal to null ```HTTP PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 @@ -81,7 +81,7 @@ PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 Notes: 1. `displayName` cannot be set to `null` because it has be marked with `Nullable="false"` in the CSDL. -### {4} Update the display name of a service principal +### 4. Update the display name of a service principal ```HTTP PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 @@ -102,7 +102,7 @@ Notes: 1. `displayName` can be set to any value other than `null` 2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. -### {5} Update the foo property of a service principal to null +### 5. Update the foo property of a service principal to null ```HTTP PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 @@ -123,7 +123,7 @@ Notes: 1. `foo` can be set to `null` because it has be marked with `Nullable="true"` in the CSDL. 2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. -### {6} Update the foo property of a service principal to a non-default value +### 6. Update the foo property of a service principal to a non-default value ```HTTP PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 @@ -144,7 +144,7 @@ Notes: 1. `foo` can be set to `something other than testval` 2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. -### {7} Update the bar property of a service principal to null +### 7. Update the bar property of a service principal to null ```HTTP PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 @@ -163,7 +163,7 @@ PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 Notes: 1. `bar` cannot be set to `null` because it has be marked with `Nullable="false"` in the CSDL. -### {8} Update the bar property of a service principal to a non-default value +### 8. Update the bar property of a service principal to a non-default value ```HTTP PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 @@ -184,7 +184,7 @@ Notes: 1. `bar` can be set to `a new bar` 2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. -### {9} Create a service principal while customizing the display name +### 9. Create a service principal while customizing the display name ```HTTP POST /servicePrincipals { @@ -206,7 +206,7 @@ Notes: 2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL 3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL -### {10} Create a service principal with a null display name +### 10. Create a service principal with a null display name ```HTTP POST /servicePrincipals { @@ -225,7 +225,7 @@ POST /servicePrincipals Notes: 1. `displayName` isn't required to create a new `servicePrincipal`, but it *can* be provided; it *cannot* be provided as `null` because the property was marked with `Nullable="false"` -### {11} Create a service principal with a value for the foo property +### 11. Create a service principal with a value for the foo property ```HTTP POST /servicePrincipals { @@ -247,7 +247,7 @@ Notes: 2. `foo` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. 3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL -### {12} Create a service principal with null for the foo property +### 12. Create a service principal with null for the foo property ```HTTP POST /servicePrincipals { @@ -269,7 +269,7 @@ Notes: 2. `foo` isn't required to create a new `servicePrincipal`, but it *can* be provided; because the property has `Nullable="true"`, a `null` value can be provided for it. 3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL -### {13} Create a service principal with a value for the bar property +### 13. Create a service principal with a value for the bar property ```HTTP POST /servicePrincipals { @@ -291,7 +291,7 @@ Notes: 2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL 3. `bar` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. -### {14} Create a service principal with null for the bar property +### 14. Create a service principal with null for the bar property ```HTTP POST /servicePrincipals { diff --git a/graph/patterns/long-running-operations.md b/graph/patterns/long-running-operations.md index cd4a8905..1f664740 100644 --- a/graph/patterns/long-running-operations.md +++ b/graph/patterns/long-running-operations.md @@ -46,10 +46,6 @@ The RELO pattern is the preferred pattern for long running operations and should used wherever possible. The pattern avoids complexity, and consistent resource presentation makes things simpler for our users and tooling chain. -In general, Microsoft Graph API guidelines for long running operations follow [Microsoft REST API -Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#13-long-running-operations). -There are some deviations from the base guidelines where Microsoft Graph API standards require that you do one of the following: - - For the RELO pattern, you should return the Location header that indicates the location of the resource. - The API response says the targeted resource is being created by returning a 201 status code and the resource URI is provided in the Location header, but the response indicates that the request is not completed by including "Provisioning" status. From 388b4ce00011526dba4217bb72286d4c09d247ba Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 21 Dec 2023 14:05:22 -0500 Subject: [PATCH 638/729] updated collections --- graph/GuidelinesGraph.md | 11 ++- graph/articles/collections.md | 163 +++++++++++++++------------------- 2 files changed, 76 insertions(+), 98 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 648799ee..5bd0e25e 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -277,11 +277,10 @@ For an additional list of standard HTTP methods, see the [RFC7230](https://www.r "code": "badRequest", "message": "Cannot process the request because a required field is missing.", "target": "query", - "innererror": { - "code": "requiredFieldMissing", - - } - } + "innererror":{ + "code": "requiredFieldMissing", + "message": "A required field is missing.", + } } ``` @@ -308,7 +307,7 @@ The top-level error code MUST match the HTTP response status code description, c "message": "Cannot process the request because it is malformed or incorrect.", "innererror": { "code": "requiredFieldOrParameterMissing", - + "message": "A required field or parameter is missing.", } } } diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 21e86371..6ae7ce07 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -1,55 +1,72 @@ -# Collections +# Collections + ## 1. Item keys -Services MAY support durable identifiers for each item in the collection, and that identifier SHOULD be represented in JSON as "id". These durable identifiers are often used as item keys. -Collections that support durable identifiers MAY support delta queries. +Services SHOULD support durable identifiers for each item in the collection, and that identifier SHOULD be represented in JSON as "id". These durable identifiers are often used as item keys. + +Collections MAY support delta queries, see the [Change Tracking pattern](../patterns/change-tracking.md) section for more details. ## 2. Serialization -Collections are represented in JSON using standard array notation. + +Collections are represented in JSON using standard array notation for `value` property. ## 3. Collection URL patterns -Collections are located directly under the service root when they are top-level, or as a segment under another resource when scoped to that resource. Collection names usually use plural nouns with no suffixes, such as "Collection" or "List". + +While there are multiple collections located directly under the Graph root going forward, you MUST have a singleton for the top-level segment and scope collections to an appropriate singleton. Collection names SHOULD be plural nouns when possible. Collection names shouldn't use suffixes, such as "Collection" or "List". For example: ```http -GET https://api.contoso.com/v1.0/people +GET https://graph.microsoft.com/v1.0/teamwork/devices ``` - Whenever possible, services MUST support the "/" pattern. +Collections elements MUST be addressable by a unique id property. The id property MUST be a String and MUST be unique within the collection. The id property MUST be represented in JSON as "id". For example: ```http -GET https://{serviceRoot}/{collection}/{id} +GET https://graph.microsoft.com/beta/teamwork/devices/0f3ce432-e432-0f3c-32e4-3c0f32e43c0f ``` Where: -- {serviceRoot} – the combination of host (site URL) + the root path to the service -- {collection} – the name of the collection, unabbreviated, pluralized -- {id} – the value of the unique id property. When using the "/" pattern this MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. + +- "https://graph.microsoft.com/beta/teamwork" - the service root represented as the combination of host (site URL) + the root path to the service. +- "devices" – the name of the collection, unabbreviated, pluralized. +- "0f3ce432-e432-0f3c-32e4-3c0f32e43c0f" – the value of the unique id property that MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. ### 3.1. Nested collections and properties + Collection items MAY contain other collections. -For example, a user collection MAY contain user resources that have multiple addresses: +For example, a devices collection MAY contain device resources that have multiple mac addresses: ```http -GET https://api.contoso.com/v1.0/people/123/addresses +GET https://graph.microsoft.com/beta/teamwork/devices/0f3ce432-e432-0f3c-32e4-3c0f32e43c0f ``` ```json + { - "value": [ - { "street": "1st Avenue", "city": "Seattle" }, - { "street": "124th Ave NE", "city": "Redmond" } - ] + "value": { + "@odata.type": "#microsoft.graph.teamworkDevice", + "id": "0f3ce432-e432-0f3c-32e4-3c0f32e43c0f", + "deviceType": "CollaborationBar", + "hardwareDetail": { + "serialNumber": "0189", + "uniqueId": "5abcdefgh", + "macAddresses": [], + "manufacturer": "yealink", + "model": "vc210" + }, + ... + } } ``` ## 4. Big collections + As data grows, so do collections. -Planning for pagination is important for all services. -Therefore, when multiple pages are available, the serialization payload MUST contain the opaque URL for the next page as appropriate. -Refer to the paging guidance for more details. +Services SHOULD support server-side pagination from day one even for all collections, as adding pagination is a breaking change. +When multiple pages are available, the serialization payload MUST contain the opaque URL for the next page as appropriate. +Refer to the [paging guidance](../Guidelines-deprecated.md#98-pagination) for more details. Clients MUST be resilient to collection data being either paged or nonpaged for any given request. @@ -66,37 +83,34 @@ Clients MUST be resilient to collection data being either paged or nonpaged for ``` ## 5. Changing collections + POST requests are not idempotent. This means that two POST requests sent to a collection resource with exactly the same payload MAY lead to multiple items being created in that collection. This is often the case for insert operations on items with a server-side generated id. +For additional information refer to [Upsert pattern](../patterns/upsert.md). For example, the following request: ```http -POST https://api.contoso.com/v1.0/people -``` +POST https://graph.microsoft.com/beta/teamwork/devices Would lead to a response indicating the location of the new collection item: ```http 201 Created -Location: https://api.contoso.com/v1.0/people/123 +Location: https://graph.microsoft.com/beta/teamwork/devices/123 ``` And once executed again, would likely lead to another resource: ```http 201 Created -Location: https://api.contoso.com/v1.0/people/124 +Location: https://graph.microsoft.com/beta/teamwork/devices/124 ``` -While a PUT request would require the indication of the collection item with the corresponding key instead: - -```http -PUT https://api.contoso.com/v1.0/people/123 -``` ## 6. Sorting collections + The results of a collection query MAY be sorted based on property values. The property is determined by the value of the _$orderBy_ query parameter. @@ -114,56 +128,54 @@ The sort order is the inherent order for the type of the property. For example: ```http -GET https://api.contoso.com/v1.0/people?$orderBy=name +GET https://graph.microsoft.com/beta/teamwork/devices?$orderBy=companyAssetTag ``` -Will return all people sorted by name in ascending order. +Will return all devices sorted by companyAssetTag in ascending order. For example: ```http -GET https://api.contoso.com/v1.0/people?$orderBy=name desc +GET https://graph.microsoft.com/beta/teamwork/devices?$orderBy=companyAssetTag desc ``` -Will return all people sorted by name in descending order. +Will return all devices sorted by companyAssetTag in descending order. Sub-sorts can be specified by a comma-separated list of property names with OPTIONAL direction qualifier. For example: ```http -GET https://api.contoso.com/v1.0/people?$orderBy=name desc,hireDate +GET https://graph.microsoft.com/beta/teamwork/devices?$orderBy=companyAssetTag desc,activityState ``` -Will return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order. - -Sorting MUST compose with filtering such that: +Will return all devices sorted by companyAssetTag in descending order and a secondary sort order of activityState in ascending order. -```http -GET https://api.contoso.com/v1.0/people?$filter=name eq 'david'&$orderBy=hireDate -``` +Sorting MUST compose with filtering see [Odata 4.01 spec](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361038) for more details. -Will return all people whose name is David sorted in ascending order by hireDate. ### 6.1. Interpreting a sorting expression + Sorting parameters MUST be consistent across pages, as both client and server-side paging is fully compatible with sorting. If a service does not support sorting by a property named in a _$orderBy_ expression, the service MUST respond with an error message as defined in the Responding to Unsupported Requests section. ## 7. Filtering + The _$filter_ querystring parameter allows clients to filter a collection of resources that are addressed by a request URL. The expression specified with _$filter_ is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Resources for which the expression evaluates to false or to null, or which reference properties that are unavailable due to permissions, are omitted from the response. -Example: return all Products whose Price is less than $10.00 +Example: return all devices with activity state equal to 'Active' ```http -GET https://api.contoso.com/v1.0/products?$filter=price lt 10.00 +GET https://graph.microsoft.com/beta/teamwork/devices?$filter=(activityState eq 'Active') ``` The value of the _$filter_ option is a Boolean expression. ### 7.1. Filter operations + Services that support _$filter_ SHOULD support the following minimal set of operations. Operator | Description | Example @@ -182,40 +194,6 @@ not | Logical negation | not price le 3.5 Grouping Operators | | ( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 -### 7.2. Operator examples -The following examples illustrate the use and semantics of each of the logical operators. - -Example: all products with a name equal to 'Milk' - -```http -GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' -``` - -Example: all products with a name not equal to 'Milk' - -```http -GET https://api.contoso.com/v1.0/products?$filter=name ne 'Milk' -``` - -Example: all products with the name 'Milk' that also have a price less than 2.55: - -```http -GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' and price lt 2.55 -``` - -Example: all products that either have the name 'Milk' or have a price less than 2.55: - -```http -GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' or price lt 2.55 -``` - -Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55: - -```http -GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 -``` - -### 7.3. Operator precedence Services MUST use the following operator precedence for supported operators when evaluating _$filter_ expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence: @@ -225,15 +203,16 @@ Operators in the same category have equal precedence: | Grouping | ( ) | Precedence grouping | | Unary | not | Logical Negation | | Relational | gt | Greater Than | -| | ge | Greater than or Equal | +| | ge | Greater Than or Equal | | | lt | Less Than | -| | le | Less than or Equal | +| | le | Less Than or Equal | | Equality | eq | Equal | | | ne | Not Equal | | Conditional AND | and | Logical And | | Conditional OR | or | Logical Or | ## 8. Pagination + RESTful APIs that return collections MAY return partial sets. Consumers of these services MUST expect partial result sets and correctly page through to retrieve an entire set. @@ -244,6 +223,7 @@ Client-driven paging enables clients to request only the number of resources tha Sorting and Filtering parameters MUST be consistent across pages, because both client- and server-side paging is fully compatible with both filtering and sorting. ### 8.1. Server-driven paging + Paginated responses MUST indicate a partial result by including a continuation token in the response. The absence of a continuation token means that no additional pages are available. @@ -252,20 +232,20 @@ Clients MUST treat the continuation URL as opaque, which means that query option Example: ```http -GET http://api.contoso.com/v1.0/people HTTP/1.1 +GET https://graph.microsoft.com/beta/teamwork/devices Accept: application/json HTTP/1.1 200 OK Content-Type: application/json { - ..., "value": [...], "@nextLink": "{opaqueUrl}" } ``` ### 8.2. Client-driven paging + Clients MAY use _$top_ and _$skip_ query parameters to specify a number of results to return and an offset into the collection. The server SHOULD honor the values specified by the client; however, clients MUST be prepared to handle responses that contain a different page size or contain a continuation token. @@ -278,19 +258,20 @@ This will avoid the risk of the client making assumptions about the data returne Example: ```http -GET http://api.contoso.com/v1.0/people?$top=5&$skip=2 HTTP/1.1 +GET https://graph.microsoft.com/beta/teamwork/devices?$top=5&$skip=2 + Accept: application/json HTTP/1.1 200 OK Content-Type: application/json { - ..., - "value": [...] + "value": [...] } ``` ### 8.3. Additional considerations + **Stable order prerequisite:** Both forms of paging depend on the collection of items having a stable order. The server MUST supplement any specified order criteria with additional sorts (typically by key) to ensure that items are always ordered consistently. @@ -310,31 +291,29 @@ If a server paginates an embedded collection, it MUST include additional continu **Recordset count:** Developers who want to know the full number of records across all pages, MAY include the query parameter _$count=true_ to tell the server to include the count of items in the response. ## 9. Compound collection operations + Filtering, Sorting and Pagination operations MAY all be performed against a given collection. When these operations are performed together, the evaluation order MUST be: 1. **Filtering**. This includes all range expressions performed as an AND operation. 2. **Sorting**. The potentially filtered list is sorted according to the sort criteria. 3. **Pagination**. The materialized paginated view is presented over the filtered, sorted list. This applies to both server-driven pagination and client-driven pagination. - + ## 10. Empty Results - + When a filter is performed on a collection and the result set is empty you MUST respond with a valid response body and a 200 response code. In this example the filters supplied by the client resulted in a empty result set. The response body is returned as normal and the _value_ attribute is set to a empty collection. -A client MAY be expecting metadata attributes like _maxItems_ based on the format of your responses to similar calls which produced results. -You SHOULD maintain consistency in your API whenever possible. +You SHOULD maintain consistency in your API whenever possible. ```http -GET https://api.contoso.com/v1.0/products?$filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55 +GET https://graph.microsoft.com/beta/teamwork/devices?$filter=('deviceType' eq 'Collab' or companyAssetTa eq 'Tag1') Accept: application/json HTTP/1.1 200 OK Content-Type: application/json { - ..., - "maxItems": 0, - "value": [] + "value": [] } ``` From 161527899cbc48cfc80d302549764044cefe86d2 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:10:57 -0500 Subject: [PATCH 639/729] server-side pagination reasoning --- graph/articles/collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 6ae7ce07..fb66e130 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -217,7 +217,7 @@ RESTful APIs that return collections MAY return partial sets. Consumers of these services MUST expect partial result sets and correctly page through to retrieve an entire set. There are two forms of pagination that MAY be supported by RESTful APIs. -Server-driven paging mitigates against denial-of-service attacks by forcibly paginating a request over multiple response payloads. +Server-driven paging allows servers to even out load across clients and mitigates against denial-of-service attacks by forcibly paginating a request over multiple response payloads. Client-driven paging enables clients to request only the number of resources that it can use at a given time. Sorting and Filtering parameters MUST be consistent across pages, because both client- and server-side paging is fully compatible with both filtering and sorting. From ea22528affafa627e4344e2f40cdb210c35b7d4c Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 21 Dec 2023 14:28:58 -0500 Subject: [PATCH 640/729] updated nextLink --- graph/articles/collections.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 6ae7ce07..159620e7 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -224,10 +224,10 @@ Sorting and Filtering parameters MUST be consistent across pages, because both c ### 8.1. Server-driven paging -Paginated responses MUST indicate a partial result by including a continuation token in the response. -The absence of a continuation token means that no additional pages are available. +Paginated responses MUST indicate a partial result by including a `@odata.nextLink` token in the response. +The absence of a `nextLink` token means that no additional pages are available, see [Odata 4.01 spec](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_ServerDrivenPaging) for more details. -Clients MUST treat the continuation URL as opaque, which means that query options may not be changed while iterating over a set of partial results. +Clients MUST treat the `nextLink` URL as opaque, which means that query options may not be changed while iterating over a set of partial results. Example: @@ -240,7 +240,7 @@ Content-Type: application/json { "value": [...], - "@nextLink": "{opaqueUrl}" + "@odata.nextLink": "{opaqueUrl}" } ``` @@ -248,7 +248,7 @@ Content-Type: application/json Clients MAY use _$top_ and _$skip_ query parameters to specify a number of results to return and an offset into the collection. -The server SHOULD honor the values specified by the client; however, clients MUST be prepared to handle responses that contain a different page size or contain a continuation token. +The server SHOULD honor the values specified by the client; however, clients MUST be prepared to handle responses that contain a different page size or contain a `@odata.nextLink` token. When both _$top_ and _$skip_ are given by a client, the server SHOULD first apply _$skip_ and then _$top_ on the collection. @@ -286,7 +286,7 @@ If the page size requested by the client is larger than the default page size su The server SHOULD honor this preference if the specified page size is smaller than the server's default page size. **Paginating embedded collections:** It is possible for both client-driven paging and server-driven paging to be applied to embedded collections. -If a server paginates an embedded collection, it MUST include additional continuation tokens as appropriate. +If a server paginates an embedded collection, it MUST include additional `nextLink` tokens as appropriate. **Recordset count:** Developers who want to know the full number of records across all pages, MAY include the query parameter _$count=true_ to tell the server to include the count of items in the response. From 3c4dacf5f933e87c894832615ae290f56dbf2a37 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 21 Dec 2023 14:38:43 -0500 Subject: [PATCH 641/729] deprecation note --- graph/Guidelines-deprecated.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/Guidelines-deprecated.md b/graph/Guidelines-deprecated.md index b22e5339..e8e3f8ca 100644 --- a/graph/Guidelines-deprecated.md +++ b/graph/Guidelines-deprecated.md @@ -1,6 +1,6 @@ > # DEPRECATION NOTICE TO READERS -> This document is being deprecated and merged with the [Microsoft Graph REST API Guidelines](./graph/Guidelines_deprecated.md)guidelines, with a removal date of 07/01/2024.. Please refer to the notes below for the latest guidance. +> This document is being deprecated and merged with the [Microsoft Graph REST API Guidelines](./graph/Guidelines_deprecated.md), with a removal date of 07/01/2024. Please refer to the notes below for the latest guidance. > > ## **Guidance for Microsoft Graph service teams** > Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. From 7653778cd47da48f820afa1d3747df7c71daf36d Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 21 Dec 2023 14:54:12 -0500 Subject: [PATCH 642/729] formatting --- graph/articles/collections.md | 10 ++++----- graph/articles/errorResponses.md | 12 +++++------ graph/articles/naming.md | 36 ++++++++++++++++++++------------ 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 397091a1..7347dfc0 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -8,7 +8,7 @@ Collections MAY support delta queries, see the [Change Tracking pattern](../patt ## 2. Serialization -Collections are represented in JSON using standard array notation for `value` property. +Collections are represented in JSON using standard array notation for `value` property. ## 3. Collection URL patterns @@ -108,7 +108,6 @@ And once executed again, would likely lead to another resource: Location: https://graph.microsoft.com/beta/teamwork/devices/124 ``` - ## 6. Sorting collections The results of a collection query MAY be sorted based on property values. @@ -153,7 +152,6 @@ Will return all devices sorted by companyAssetTag in descending order and a seco Sorting MUST compose with filtering see [Odata 4.01 spec](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361038) for more details. - ### 6.1. Interpreting a sorting expression Sorting parameters MUST be consistent across pages, as both client and server-side paging is fully compatible with sorting. @@ -301,9 +299,9 @@ When these operations are performed together, the evaluation order MUST be: ## 10. Empty Results -When a filter is performed on a collection and the result set is empty you MUST respond with a valid response body and a 200 response code. -In this example the filters supplied by the client resulted in a empty result set. -The response body is returned as normal and the _value_ attribute is set to a empty collection. +When a filter is performed on a collection and the result set is empty you MUST respond with a valid response body and a 200 response code. +In this example the filters supplied by the client resulted in a empty result set. +The response body is returned as normal and the _value_ attribute is set to a empty collection. You SHOULD maintain consistency in your API whenever possible. ```http diff --git a/graph/articles/errorResponses.md b/graph/articles/errorResponses.md index 13d1b99f..ea573925 100644 --- a/graph/articles/errorResponses.md +++ b/graph/articles/errorResponses.md @@ -21,7 +21,7 @@ Services can avoid breaking changes by adding new error codes to "innererror" in The value for the "message" name/value pair MUST be a human-readable representation of the error. It is intended as an aid to developers and is not suitable for exposure to end users. -Services wanting to expose a suitable message for end users MUST do so through an [annotation][odata-json-annotations] or custom property. +Services wanting to expose a suitable message for end users MUST do so through an [odata-json-annotations](https://docs.oasis-open.org/odata/odata-json-format/v4.01/cs02/odata-json-format-v4.01-cs02.html#sec_AnnotateaJSONObject) or custom property. Services SHOULD NOT localize "message" for the end user, because doing so might make the value unreadable to the app developer who may be logging the value, as well as make the value less searchable on the Internet. The value for the "target" name/value pair is the target of the particular error (e.g., the name of the property in error). @@ -42,17 +42,17 @@ Error objects MAY also include custom server-defined name/value pairs that MAY b Error types with custom server-defined properties SHOULD be declared in the service's metadata document. See example below. -Error responses MAY contain [annotations][odata-json-annotations] in any of their JSON objects. +Error responses MAY contain Odata JSON annotations in any of their JSON objects. We recommend that for any transient errors that may be retried, services SHOULD include a Retry-After HTTP header indicating the minimum number of seconds that clients SHOULD wait before attempting the operation again. -##### ErrorResponse : Object +## ErrorResponse : Object Property | Type | Required | Description -------- | ---- | -------- | ----------- `error` | Error | ✔ | The error object. -##### Error : Object +## Error : Object Property | Type | Required | Description -------- | ---- | -------- | ----------- @@ -62,14 +62,14 @@ Property | Type | Required | Description `details` | Error[] | | An array of details about specific errors that led to this reported error. `innererror` | InnerError | | An object containing more specific information than the current object about the error. -##### InnerError : Object +## InnerError : Object Property | Type | Required | Description -------- | ---- | -------- | ----------- `code` | String | | A more specific error code than was provided by the containing error. `innererror` | InnerError | | An object containing more specific information than the current object about the error. -##### Examples +## Examples Example of "innererror": diff --git a/graph/articles/naming.md b/graph/articles/naming.md index 70caef29..b14bd69b 100644 --- a/graph/articles/naming.md +++ b/graph/articles/naming.md @@ -1,51 +1,61 @@ -# Naming -## 1. Approach +# Naming + +## 1. Approach + Naming policies should aid developers in discovering functionality without having to constantly refer to documentation. Use of common patterns and standard conventions greatly aids developers in correctly guessing common property names and meanings. Services SHOULD use verbose naming patterns and MUST NOT use abbreviations other than acronyms that are the dominant mode of expression in the domain being represented by the API, (e.g. Url). -## 2. Casing +## 2. Casing + - Acronyms SHOULD follow the casing conventions as though they were regular words (e.g. Url). - All identifiers including namespaces, entityTypes, entitySets, properties, actions, functions and enumeration values MUST use lowerCamelCase. - HTTP headers are the exception and SHOULD use standard HTTP convention of Capitalized-Hyphenated-Terms. -## 3. Names to avoid +## 3. Names to avoid + Certain names are so overloaded in API domains that they lose all meaning or clash with other common usages in domains that cannot be avoided when using REST APIs, such as OAUTH. Services SHOULD NOT use the following names: + - Context - Scope - Resource -## 4. Forming compound names +## 4. Forming compound names + - Services SHOULD avoid using articles such as 'a', 'the', 'of' unless needed to convey meaning. - - e.g. names such as aUser, theAccount, countOfBooks SHOULD NOT be used, rather user, account, bookCount SHOULD be preferred. + - e.g. names such as aUser, theAccount, countOfBooks SHOULD NOT be used, rather user, account, bookCount SHOULD be preferred. - Services SHOULD add a type to a property name when not doing so would cause ambiguity about how the data is represented or would cause the service not to use a common property name. - When adding a type to a property name, services MUST add the type at the end, e.g. createdDateTime. -## 5. Identity properties +## 5. Identity properties + - Services MUST use string types for identity properties. - For OData services, the service MUST use the OData @id property to represent the canonical identifier of the resource. - Services MAY use the simple 'id' property to represent a local or legacy primary key value for a resource. - Services SHOULD use the name of the relationship postfixed with 'Id' to represent a foreign key to another resource, e.g. subscriptionId. - - The content of this property SHOULD be the canonical ID of the referenced resource. + - The content of this property SHOULD be the canonical ID of the referenced resource. -## 6. Date and time properties +## 6. Date and time properties - For properties requiring both date and time, services MUST use the suffix 'DateTime'. - For properties requiring only date information without specifying time, services MUST use the suffix 'Date', e.g. birthDate. - For properties requiring only time information without specifying date, services MUST use the suffix 'Time', e.g. appointmentStartTime. -## 7. Name properties +## 7. Name properties + - For the overall name of a resource typically shown to users, services MUST use the property name 'displayName'. - Services MAY use other common naming properties, e.g. givenName, surname, signInName. -## 8. Collections and counts +## 8. Collections and counts + - Services MUST name collections as plural nouns or plural noun phrases using correct English. - Services MAY use simplified English for nouns that have plurals not in common verbal usage. - - e.g. schemas MAY be used instead of schemata. + - e.g. schemas MAY be used instead of schemata. - Services MUST name counts of resources with a noun or noun phrase suffixed with 'Count'. -## 9. Common property names +## 9. Common property names + Where services have a property, whose data matches the names below, the service MUST use the name from this table. This table will grow as services add terms that will be more commonly used. Service owners adding such terms SHOULD propose additions to this document. From e8499d8ee144e1b9316b4e0bfddc484cfd09a9e6 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 21 Dec 2023 15:06:54 -0500 Subject: [PATCH 643/729] fixed files --- graph/Guidelines-deprecated.md | 3 +- graph/GuidelinesGraph.md | 2 +- graph/{ => articles}/deprecation.md | 0 graph/nullable.md | 311 ---------------------------- 4 files changed, 3 insertions(+), 313 deletions(-) rename graph/{ => articles}/deprecation.md (100%) delete mode 100644 graph/nullable.md diff --git a/graph/Guidelines-deprecated.md b/graph/Guidelines-deprecated.md index e8e3f8ca..5ad38bd9 100644 --- a/graph/Guidelines-deprecated.md +++ b/graph/Guidelines-deprecated.md @@ -1,7 +1,8 @@ > # DEPRECATION NOTICE TO READERS +> > This document is being deprecated and merged with the [Microsoft Graph REST API Guidelines](./graph/Guidelines_deprecated.md), with a removal date of 07/01/2024. Please refer to the notes below for the latest guidance. -> +> > ## **Guidance for Microsoft Graph service teams** > Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. > diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 5bd0e25e..ba9deffb 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -384,7 +384,7 @@ Microsoft Graph APIs in the GA version guarantee API stability and consistency f On the beta endpoint, breaking changes and deprecation of APIs are allowed with consideration of dependencies and customer impact. It is a best practice to test new element versions on the beta endpoint at first and then promote API changes to the GA endpoint. -Detailed requirements for versioning and deprecation are described in the [Deprecation guidelines](./deprecation.md). +Detailed requirements for versioning and deprecation are described in the [Deprecation guidelines](./articles/deprecation.md). ## Recommended API design patterns diff --git a/graph/deprecation.md b/graph/articles/deprecation.md similarity index 100% rename from graph/deprecation.md rename to graph/articles/deprecation.md diff --git a/graph/nullable.md b/graph/nullable.md deleted file mode 100644 index 7e581620..00000000 --- a/graph/nullable.md +++ /dev/null @@ -1,311 +0,0 @@ -# Nullable Properties - -A nullable property means *only* that the property may have `null` as a value; the "nullability" of a property does not say anything about how a value is set into a property. -For example, a non-nullable property is *not* required to create a new instance of an entity. -It only means that the property will have a value when it is retrieved. -In the case that no value is provided when the entity is created, this means that the service will create one; this value can be specified with the `DefaultValue` attribute, but if the value is contextual and determine at request time, then the property can both be non-nullable *and* have no `DefaultValue` specified. -Below are some examples of nullable and non-nullable properties. - -## CSDL - -```xml - -... - - - - - - - - - - ... - -``` - -## HTTP Requests - -### {1} Create a servicePrincipal with no properties - -```HTTP -POST /servicePrincipals - -400 Bad Request -{ - "error": { - "code": "badRequest", - "message": "The 'appId' property is required to create a servicePrincipal." - } -} -``` - -### {2} Create a servicePrincipal without a display name - -```HTTP -POST /servicePrincipals -{ - "appId": "00000000-0000-0000-0000-000000000001" -} - -201 Created -{ - "appId": "00000000-0000-0000-0000-000000000001", - "displayName": "some application name", - "foo": "testval", - "bar": "differentvalue", - ... -} -``` -Notes: -1. `displayName` was given a value by the service even though no value was provided by the client -2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL -3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL - -### {3} Update the display name of a service principal to null - -```HTTP -PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 -{ - "displayName": null -} - -400 Bad Request -{ - "error": { - "code": "badRequest", - "message": "null is not a valid value for the property 'displayName'; 'displayName' is not a nullable property." - } -} -``` -Notes: -1. `displayName` cannot be set to `null` because it has be marked with `Nullable="false"` in the CSDL. - -### {4} Update the display name of a service principal - -```HTTP -PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 -{ - "displayName": "a non-generated display name" -} - -200 OK -{ - "appId": "00000000-0000-0000-0000-000000000001", - "displayName": "a non-generated display name", - "foo": "testval", - "bar": "differentvalue", - ... -} -``` -Notes: -1. `displayName` can be set to any value other than `null` -2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. - -### {5} Update the foo property of a service principal to null - -```HTTP -PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 -{ - "foo": null -} - -200 OK -{ - "appId": "00000000-0000-0000-0000-000000000001", - "displayName": "a non-generated display name", - "foo": null, - "bar": "differentvalue", - ... -} -``` -Notes: -1. `foo` can be set to `null` because it has be marked with `Nullable="true"` in the CSDL. -2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. - -### {6} Update the foo property of a service principal to a non-default value - -```HTTP -PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 -{ - "foo": "something other than testval" -} - -200 OK -{ - "appId": "00000000-0000-0000-0000-000000000001", - "displayName": "a non-generated display name", - "foo": "something other than testval", - "bar": "differentvalue", - ... -} -``` -Notes: -1. `foo` can be set to `something other than testval` -2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. - -### {7} Update the bar property of a service principal to null - -```HTTP -PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 -{ - "bar": null -} - -400 Bad Request -{ - "error": { - "code": "badRequest", - "message": "null is not a valid value for the property 'bar'; 'bar' is not a nullable property." - } -} -``` -Notes: -1. `bar` cannot be set to `null` because it has be marked with `Nullable="false"` in the CSDL. - -### {8} Update the bar property of a service principal to a non-default value - -```HTTP -PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 -{ - "bar": "a new bar" -} - -200 OK -{ - "appId": "00000000-0000-0000-0000-000000000001", - "displayName": "a non-generated display name", - "foo": "something other than testval", - "bar": "a new bar", - ... -} -``` -Notes: -1. `bar` can be set to `a new bar` -2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. - -### {9} Create a service principal while customizing the display name -```HTTP -POST /servicePrincipals -{ - "appId": "00000000-0000-0000-0000-000000000001", - "displayName": "a different name" -} - -201 Created -{ - "appId": "00000000-0000-0000-0000-000000000001", - "displayName": "a different name", - "foo": "testval", - "bar": "differentvalue", - ... -} -``` -Notes: -1. `displayName` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. -2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL -3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL - -### {10} Create a service principal with a null display name -```HTTP -POST /servicePrincipals -{ - "appId": "00000000-0000-0000-0000-000000000001", - "displayName": null -} - -400 Bad Request -{ - "error": { - "code": "badRequest", - "message": "null is not a valid value for the property 'displayName'; 'displayName' is not a nullable property." - } -} -``` -Notes: -1. `displayName` isn't required to create a new `servicePrincipal`, but it *can* be provided; it *cannot* be provided as `null` because the property was marked with `Nullable="false"` - -### {11} Create a service principal with a value for the foo property -```HTTP -POST /servicePrincipals -{ - "appId": "00000000-0000-0000-0000-000000000001", - "foo": "a foo value on creation" -} - -201 Created -{ - "appId": "00000000-0000-0000-0000-000000000001", - "displayName": "some application name", - "foo": "a foo value on creation", - "bar": "differentvalue", - ... -} -``` -Notes: -1. `displayName` was given a value by the service even though no value was provided by the client -2. `foo` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. -3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL - -### {12} Create a service principal with null for the foo property -```HTTP -POST /servicePrincipals -{ - "appId": "00000000-0000-0000-0000-000000000001", - "foo": null -} - -201 Created -{ - "appId": "00000000-0000-0000-0000-000000000001", - "displayName": "some application name", - "foo": null, - "bar": "differentvalue", - ... -} -``` -Notes: -1. `displayName` was given a value by the service even though no value was provided by the client -2. `foo` isn't required to create a new `servicePrincipal`, but it *can* be provided; because the property has `Nullable="true"`, a `null` value can be provided for it. -3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL - -### {13} Create a service principal with a value for the bar property -```HTTP -POST /servicePrincipals -{ - "appId": "00000000-0000-0000-0000-000000000001", - "bar": "running out of ideas for value names" -} - -201 Created -{ - "appId": "00000000-0000-0000-0000-000000000001", - "displayName": "some application name", - "foo": "testval", - "bar": "running out of ideas for value names", - ... -} -``` -Notes: -1. `displayName` was given a value by the service even though no value was provided by the client -2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL -3. `bar` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. - -### {14} Create a service principal with null for the bar property -```HTTP -POST /servicePrincipals -{ - "appId": "00000000-0000-0000-0000-000000000001", - "bar": null -} - -400 Bad Request -{ - "error": { - "code": "badRequest", - "message": "null is not a valid value for the property 'bar'; 'bar' is not a nullable property." - } -} -``` -Notes: -1. `bar` isn't required to create a new `servicePrincipal`, but it *can* be provided; it *cannot* be provided as `null` because the property was marked with `Nullable="false"` From 04371568169d964543881ed0626cf6ea43cbafc8 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Thu, 21 Dec 2023 15:09:10 -0500 Subject: [PATCH 644/729] Update Guidelines-deprecated.md --- graph/Guidelines-deprecated.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graph/Guidelines-deprecated.md b/graph/Guidelines-deprecated.md index 5ad38bd9..a59f6ae4 100644 --- a/graph/Guidelines-deprecated.md +++ b/graph/Guidelines-deprecated.md @@ -1,9 +1,10 @@ > # DEPRECATION NOTICE TO READERS > -> This document is being deprecated and merged with the [Microsoft Graph REST API Guidelines](./graph/Guidelines_deprecated.md), with a removal date of 07/01/2024. Please refer to the notes below for the latest guidance. +> This document is being deprecated and merged with the [Microsoft Graph REST API Guidelines](GuidelinesGraph.md), with a removal date of 07/01/2024. Please refer to the notes below for the latest guidance. > > ## **Guidance for Microsoft Graph service teams** +> > Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. > --- From 4f8698e9d62f8d2971ce9bc27de1e8c10078ace0 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:38:08 -0500 Subject: [PATCH 645/729] date format --- graph/Guidelines-deprecated.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/Guidelines-deprecated.md b/graph/Guidelines-deprecated.md index a59f6ae4..9bf636cc 100644 --- a/graph/Guidelines-deprecated.md +++ b/graph/Guidelines-deprecated.md @@ -1,7 +1,7 @@ > # DEPRECATION NOTICE TO READERS > -> This document is being deprecated and merged with the [Microsoft Graph REST API Guidelines](GuidelinesGraph.md), with a removal date of 07/01/2024. Please refer to the notes below for the latest guidance. +> This document is being deprecated and merged with the [Microsoft Graph REST API Guidelines](GuidelinesGraph.md), with a removal date of July 1, 2024. Please refer to the notes below for the latest guidance. > > ## **Guidance for Microsoft Graph service teams** > From 21c018cadfae453c1d7ece89fa069c3a8ba41317 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Fri, 22 Dec 2023 12:01:11 -0500 Subject: [PATCH 646/729] fixed inner error message --- graph/GuidelinesGraph.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 28b48998..a384618b 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -269,7 +269,7 @@ For an additional list of standard HTTP methods, see the [RFC7230](https://www.r ### Error handling - To improve API traceability and consistency you MUST use the recommended Microsoft Graph error model and the Microsoft Graph utilities library to provide a standard implementation for your service: + To improve API traceability and consistency you MUST use the recommended Microsoft Graph error model and the Microsoft Graph utilities library to provide a standard implementation for your service. The value for the "message" name/value pair MUST be a human-readable representation of the error, tailored to provide enough information for the developer to understand the error and take appropriate action. The message is intended only as an aid to developers and should not be exposed to end users. ```http { @@ -278,8 +278,7 @@ For an additional list of standard HTTP methods, see the [RFC7230](https://www.r "message": "Cannot process the request because a required field is missing.", "target": "query", "innererror":{ - "code": "requiredFieldMissing", - "message": "A required field is missing.", + "code": "requiredFieldMissing" } } ``` @@ -304,10 +303,9 @@ The top-level error code MUST match the HTTP response status code description, c { "error": { "code": "badRequest", - "message": "Cannot process the request because it is malformed or incorrect.", + "message": "Cannot process the request because a required field is missing.", "innererror": { - "code": "requiredFieldOrParameterMissing", - "message": "A required field or parameter is missing.", + "code": "requiredFieldOrParameterMissing" } } } @@ -320,7 +318,7 @@ The top-level error code MUST match the HTTP response status code description, c | :heavy_check_mark: **MUST** return a 429 Too Many Requests error when the client exceeded throttling limits, and a 503 Service Unavailable error when the service overloaded but the client is within throttling limits.| | :ballot_box_with_check: **SHOULD** return a 404 Not Found error if a 403 error would result in information disclosure. | -For additional guidance, see [Error condition responses article](./articles/errorResponses.md). +For more detailed guidance, see the article on [Error condition responses](./articles/errorResponses.md). For a complete mapping of error codes to HTTP statuses, see [rfc7231 (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7231#section-6). From 84a714310314885f1ff68a53bd5e6aa81ae506f8 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:11:33 -0500 Subject: [PATCH 647/729] Update naming guidance for id properties, transitiveChildren properties, and completedDateTime common property name (#507) --- graph/GuidelinesGraph.md | 8 +++++ graph/articles/naming.md | 71 ++++++++++++++++++++-------------------- 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index a384618b..0e61bd5c 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -104,6 +104,7 @@ Following is a short summary of the most often used conventions. | **CASING** | | | :heavy_check_mark: **MUST** use lower camel case for *all* names and namespaces. | - **Right:** automaticRepliesStatus
    - **Wrong:** kebab-case or snake_case | | :ballot_box_with_check: **SHOULD** case two-letter acronyms with the same case. | - **Right:** ioLimit or totalIOAmount
    - **Right:** 'id' properties similar to driveId or applicationId
    - **Wrong:** iOLimit or totalIoAmount| +| :ballot_box_with_check: **SHOULD** case `id` properties the same as a normal word. | - **Right:** id or fileId
    - **Wrong:** ID or fileID | | :ballot_box_with_check: **SHOULD** case three+ letter acronyms the same as a normal word. | - **Right:** fidoKey or oauthUrl
    - **Wrong:** webHTML | | :no_entry: **MUST NOT** capitalize the word following a [prefix](https://www.thoughtco.com/common-prefixes-in-english-1692724) or words within a [compound word](http://www.learningdifferences.com/Main%20Page/Topics/Compound%20Word%20Lists/Compound_Word_%20Lists_complete.htm). | - **Right:** subcategory, geo coordinate, or crosswalk
    - **Wrong:** metaData, semiCircle, or airPlane | | :heavy_check_mark: **MUST** capitalize within hyphenated and open (spaced) compound words. | - **Right:** fiveYearOld, daughterInLaw, or postOffice
    - **Wrong:** paperclip or fullmoon | @@ -116,6 +117,13 @@ Following is a short summary of the most often used conventions. | :no_entry: **MUST NOT** use collection, response, or request suffixes. | - **Right:** addresses
    - **Wrong:** addressCollection | | :no_entry: **MUST NOT** contain product names. | - **Right:** chatMessages
    - **Wrong:** teamsMessages | +#### Tree and graph structures + +When a tree or graph structure is being modeled, the direct sub-nodes are usually named `children` or `members`. +If a property is desired that represents a "flattened" data structure, the property **SHOULD** be prefixed with "transitive", e.g. `transitiveChildren` or `transitiveMembers`. +Such properties **MUST** represent a mathematically [transitive relationship](https://en.wikipedia.org/wiki/Transitive_relation). +To simplify, if `A` is a child of `B` and `B` is a child of `C`, then `A` is a child of `C` via a transitive relationship; this **MUST** be true for properties using word "transitive" in their name. + ### Uniform Resource Locators (URLs) A Uniform Resource Locator (URL) is how developers access the resources of your API. diff --git a/graph/articles/naming.md b/graph/articles/naming.md index b14bd69b..0489436e 100644 --- a/graph/articles/naming.md +++ b/graph/articles/naming.md @@ -60,39 +60,40 @@ Where services have a property, whose data matches the names below, the service This table will grow as services add terms that will be more commonly used. Service owners adding such terms SHOULD propose additions to this document. -| | -|------------- | - attendees | - body | - createdDateTime | - childCount | - children | - contentUrl | - country | - createdBy | - displayName | - errorUrl | - eTag | - event | - expirationDateTime | - givenName | - jobTitle | - kind | - id | +| | | +|-------------------- | - | + attendees | + body | + completedDateTime | **NOTE** completionDateTime may be used for cases where the timestamp represents a point in the future | + createdDateTime | + childCount | + children | + contentUrl | + country | + createdBy | + displayName | + errorUrl | + eTag | + event | + expirationDateTime | + givenName | + jobTitle | + kind | + id | lastModifiedDateTime | - location | - memberOf | - message | - name | - owner | - people | - person | - postalCode | - photo | - preferredLanguage | - properties | - signInName | - surname | - tags | - userPrincipalName | - webUrl | + location | + memberOf | + message | + name | + owner | + people | + person | + postalCode | + photo | + preferredLanguage | + properties | + signInName | + surname | + tags | + userPrincipalName | + webUrl | From 92c7c95bf91587c05e4387d25ddf49489ac130a3 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:06:09 -0800 Subject: [PATCH 648/729] Create filter-as-segment.md --- graph/articles/filter-as-segment.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 graph/articles/filter-as-segment.md diff --git a/graph/articles/filter-as-segment.md b/graph/articles/filter-as-segment.md new file mode 100644 index 00000000..b7819edd --- /dev/null +++ b/graph/articles/filter-as-segment.md @@ -0,0 +1,3 @@ +# Filter as segment + +There is an OData feature which allows From b7d11105781f479a11e9a64307bc618fcf6b99be Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:33:12 -0800 Subject: [PATCH 649/729] Update filter-as-segment.md --- graph/articles/filter-as-segment.md | 50 ++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/graph/articles/filter-as-segment.md b/graph/articles/filter-as-segment.md index b7819edd..91378b45 100644 --- a/graph/articles/filter-as-segment.md +++ b/graph/articles/filter-as-segment.md @@ -1,3 +1,51 @@ # Filter as segment -There is an OData feature which allows +There is an [OData feature](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_AddressingaSubsetofaCollection) which allows having a `$filter` in a URL segment. +This feature is useful whenever there are operations on a collection and the client wants to perform those operations on a *subset* of the collection. +For example, the `riskyUsers` API on Microsoft Graph has an action defined to let clients "dismiss" risky users (i.e. consider those users "not risky"): + +```xml + + + + +``` + +Using this action, clients can call + +```http +POST /identityProtection/riskyUsers/dismiss +{ + "userIds": [ + "{userId1}", + "{userId2}", + ... + ] +} +``` + +in order to dismiss the risky users with the provided IDs. Using the filter-as-segment OData feature, the action could instead be defined as: + +```xml + + + +``` + +and clients could call: + +```http +POST /identityProtection/riskyUsers/$filter=@f/dismiss?@f=id IN ({userId1},{userId2},...) +``` + +Doing this is beneficial due to the robust nature of OData filter expressions: clients will be able to dismiss risky users based on any supported filter without the service team needing to implement a new `dismiss` overload that filters based on the new criteria. +However, there are some concerns about the discoverability of using the filter-as-segment feature, as well as the support of [parameter aliasing](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_ParameterAliases) that's required. +As a result, functions should be introduced that act in the same way as the filter-as-segment: + +```xml + + + + + +``` From b84171a78dbf5d4f18f0aa5ca71432117cd182af Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:35:42 -0800 Subject: [PATCH 650/729] Update filter-as-segment.md --- graph/articles/filter-as-segment.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/graph/articles/filter-as-segment.md b/graph/articles/filter-as-segment.md index 91378b45..b7da9841 100644 --- a/graph/articles/filter-as-segment.md +++ b/graph/articles/filter-as-segment.md @@ -6,7 +6,7 @@ For example, the `riskyUsers` API on Microsoft Graph has an action defined to le ```xml - + ``` @@ -32,10 +32,10 @@ in order to dismiss the risky users with the provided IDs. Using the filter-as-s ``` -and clients could call: +and clients could call ```http -POST /identityProtection/riskyUsers/$filter=@f/dismiss?@f=id IN ({userId1},{userId2},...) +POST /identityProtection/riskyUsers/$filter=@f/dismiss?@f=id IN ('{userId1}','{userId2}',...) ``` Doing this is beneficial due to the robust nature of OData filter expressions: clients will be able to dismiss risky users based on any supported filter without the service team needing to implement a new `dismiss` overload that filters based on the new criteria. @@ -43,9 +43,15 @@ However, there are some concerns about the discoverability of using the filter-a As a result, functions should be introduced that act in the same way as the filter-as-segment: ```xml - - + + - + ``` + +Clients would now be able to call + +```http +POST /identityProtection/riskyUsers/filter(expression='id IN (''{userId1}'',''{userId2}'',...)')/dismiss +``` From 651933d0e57fd17ed7569e46f065fb87b6470fa0 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:37:02 -0800 Subject: [PATCH 651/729] Update filter-as-segment.md --- graph/articles/filter-as-segment.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graph/articles/filter-as-segment.md b/graph/articles/filter-as-segment.md index b7da9841..56f81813 100644 --- a/graph/articles/filter-as-segment.md +++ b/graph/articles/filter-as-segment.md @@ -55,3 +55,7 @@ Clients would now be able to call ```http POST /identityProtection/riskyUsers/filter(expression='id IN (''{userId1}'',''{userId2}'',...)')/dismiss ``` + +NOTE: the `'` literal in the filter expression must be escaped with `''` + +An example implementation of a filter function can be found [here](TODO). From 9cec1749038df37e7ee034e6be26d444e9d5f824 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:37:30 -0800 Subject: [PATCH 652/729] Update filter-as-segment.md --- graph/articles/filter-as-segment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/filter-as-segment.md b/graph/articles/filter-as-segment.md index 56f81813..6b067b01 100644 --- a/graph/articles/filter-as-segment.md +++ b/graph/articles/filter-as-segment.md @@ -58,4 +58,4 @@ POST /identityProtection/riskyUsers/filter(expression='id IN (''{userId1}'',''{u NOTE: the `'` literal in the filter expression must be escaped with `''` -An example implementation of a filter function can be found [here](TODO). +An example implementation of a filter function using OData WebApi can be found [here](TODO). From dc86042ff5d4834a16640fc3015dd22e6b2eaef8 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:44:49 -0800 Subject: [PATCH 653/729] Update filter-as-segment.md --- graph/articles/filter-as-segment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/filter-as-segment.md b/graph/articles/filter-as-segment.md index 6b067b01..addaa0e0 100644 --- a/graph/articles/filter-as-segment.md +++ b/graph/articles/filter-as-segment.md @@ -58,4 +58,4 @@ POST /identityProtection/riskyUsers/filter(expression='id IN (''{userId1}'',''{u NOTE: the `'` literal in the filter expression must be escaped with `''` -An example implementation of a filter function using OData WebApi can be found [here](TODO). +An example implementation of a filter function using OData WebApi can be found [here](https://github.com/OData/AspNetCoreOData/commit/7732f7e6b812d9a79a73529562f2e74b68e2794f). From f90d48a408298aef4f485df47405f34417637e81 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:24:53 -0500 Subject: [PATCH 654/729] Update CODEOWNERS fixed graph owners to include nested folders and files --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f77559c4..e4ac6386 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # These are the set of folks who should review PRs on the azureRestUpdates branch. -* @microsoft/azure-api-stewardship-board @Azure/api-stewardship-board -/graph/* @microsoft/graphguidelinesapprovers +* @microsoft/azure-api-stewardship-board @Azure/api-stewardship-board +/graph/ @microsoft/graphguidelinesapprovers From 641a29e9dcf8e511e8ad221781864362aa7ae393 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:26:43 -0500 Subject: [PATCH 655/729] Update CODEOWNERS commented azure owners as the group list has errors --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e4ac6386..0c1a84a1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # These are the set of folks who should review PRs on the azureRestUpdates branch. -* @microsoft/azure-api-stewardship-board @Azure/api-stewardship-board +#* @microsoft/azure-api-stewardship-board @Azure/api-stewardship-board /graph/ @microsoft/graphguidelinesapprovers From 8ac2a813687390583b2f9519fdb33f7486c3caa5 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 31 Jan 2024 08:21:46 -0800 Subject: [PATCH 656/729] Update dictionary.md --- graph/patterns/dictionary.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 3b70af4a..a05e5a92 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -41,13 +41,18 @@ For more information, see the [OData reference](https://github.com/oasis-tcs/oda The following example demonstrates defining a dictionary that can contain string values. ```xml - - - - Edm.String - - - + + + + + + + + Edm.String + + + + ``` ### Defining a dictionary property From 8ff72bbc635bb7dde24d8f99d4556f75ed3eb84d Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 31 Jan 2024 08:23:18 -0800 Subject: [PATCH 657/729] Update dictionary.md --- graph/patterns/dictionary.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index a05e5a92..c1c870e4 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -45,7 +45,7 @@ The following example demonstrates defining a dictionary that can contain string - + //// TODO does this need to be marked as an open type? Edm.String @@ -55,6 +55,9 @@ The following example demonstrates defining a dictionary that can contain string ``` +Please note that schema validation will fail due to the casing of `Dictionary`. +This warning should be suppressed. + ### Defining a dictionary property The following example shows defining a dictionary property, "userTags", on the item entity type. From 5579a9d4155d8807f1aa80cff6e2e650eec372ba Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 31 Jan 2024 08:23:32 -0800 Subject: [PATCH 658/729] Update dictionary.md --- graph/patterns/dictionary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index c1c870e4..1f64edf4 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -56,7 +56,7 @@ The following example demonstrates defining a dictionary that can contain string ``` Please note that schema validation will fail due to the casing of `Dictionary`. -This warning should be suppressed. +This warning should be suppressed. //// TODO file issue for this ### Defining a dictionary property The following example shows defining a dictionary property, "userTags", on the item entity type. From 22a90f90390b98ca04930fc472b40e10afade5e7 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 31 Jan 2024 08:34:47 -0800 Subject: [PATCH 659/729] Update dictionary.md --- graph/patterns/dictionary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 1f64edf4..357a347d 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -45,7 +45,7 @@ The following example demonstrates defining a dictionary that can contain string - //// TODO does this need to be marked as an open type? + Edm.String From b845104b5849b122cef058ea77f476f9e4fcef4c Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 31 Jan 2024 08:37:51 -0800 Subject: [PATCH 660/729] Update dictionary.md --- graph/patterns/dictionary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 357a347d..125d87ce 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -56,7 +56,7 @@ The following example demonstrates defining a dictionary that can contain string ``` Please note that schema validation will fail due to the casing of `Dictionary`. -This warning should be suppressed. //// TODO file issue for this +This warning should be suppressed. ### Defining a dictionary property The following example shows defining a dictionary property, "userTags", on the item entity type. From 3431e24ff6469b14a2fe4aa6cdccd5164e24201b Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 28 Jan 2024 14:12:47 -0600 Subject: [PATCH 661/729] Add guidelines on returning string offsets & lengths --- azure/ConsiderationsForServiceDesign.md | 83 ++++++++++++++++++++----- azure/Guidelines.md | 13 +++- 2 files changed, 80 insertions(+), 16 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 0b37c2f4..c219c1e4 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -293,12 +293,12 @@ The operation is initiated with a POST operation and the operation path ends in ```text POST /:?api-version=2022-05-01 -Operation-Id: 22 - -{ - "arg1": 123 - "arg2": "abc" -} +Operation-Id: 22 + +{ + "arg1": 123 + "arg2": "abc" +} ``` The response is a `202 Accepted` as described above. @@ -306,7 +306,7 @@ The response is a `202 Accepted` as described above. ```text HTTP/1.1 202 Accepted Operation-Location: https:///22 - + { "id": "22", "status": "NotStarted" @@ -323,7 +323,7 @@ When the operation completes successfully, the result (if there is one) will be ```text HTTP/1.1 200 OK - + { "id": "22", "status": "Succeeded", @@ -344,7 +344,7 @@ PUT /items/FooBar&api-version=2022-05-01 Operation-Id: 22 { - "prop1": 555, + "prop1": 555, "prop2": "something" } ``` @@ -358,13 +358,13 @@ The response may also include an `Operation-Location` header for backward compat If the resource supports ETags, the response may contain an `etag` header and possibly an `etag` property in the resource. ```text -HTTP/1.1 201 Created +HTTP/1.1 201 Created Operation-Id: 22 Operation-Location: https://items/operations/22 etag: "123abc" { - "id": "FooBar", + "id": "FooBar", "etag": "123abc", "prop1": 555, "prop2": "something" @@ -381,7 +381,7 @@ When the additional processing completes, the status monitor will indicate if it ```text HTTP/1.1 200 OK - + { "id": "22", "status": "Succeeded" @@ -412,8 +412,8 @@ POST /:cancel?api-version=2022-05-01 A successful response to a control operation should be a `200 OK` with a representation of the status monitor. ```text -HTTP/1.1 200 OK - +HTTP/1.1 200 OK + { "id": "22", "status": "Canceled" @@ -515,6 +515,61 @@ For example, the client can specify an `If-Match` header with the last ETag valu The service processes the update only if the ETag value in the header matches the ETag of the current resource on the server. By computing and returning ETags for your resources, you enable clients to avoid using a strategy where the "last write always wins." +## Returning String Offsets & Lengths (Substrings) + +Some Azure services return substring offset & length values within a string. For example, the offset & length within a string to a name, email address, or phone #. +When a service response includes a string, the client's programming language deserializes that string into that language's internal string encoding. Below are the possible encodings and examples of languages that use each encoding: + +| Encoding | Example languages | +| -------- | ------- | +| UTF-8 | Go, Rust, Ruby, PHP | +| UTF-16 | JavaScript, Java, C# | +| CodePoint (UTF-32) | Python | + +Because the service doesn't know what language a client is written in and what string encoding that language uses, the service can't return UTF-agnostic offset and length values that the client can use to index within the string. To address this, the service response must include offset & length values for all 3 possible encodings and then the client code must select the encoding it required by its language's internal string encoding. + +For example, if a service response needed to identify offset & length values for "name" and "email" substrings, the JSON response would look like this: + +``` +{ + (... other properties not shown...) + "fullString": "(...some string containing a name and an email address...)", + "name": { + "offset": { + "utf8": 12, + "utf16": 10, +      "codePoint": 4 +    }, +    "length": { +    "uft8": 10, +      "utf16": 8, +      "codePoint": 2 +    } +  }, +  "email": { +  "offset": { +      "utf8": 12, +      "utf16": 10, +      "codePoint": 4 +    }, +    "length": { +      "uft8": 10, +      "utf16": 8, +      "codePoint": 4 +    } +  } +} +``` + +Then, the Go developer, for example, would get the substring containing the name using code like this: + +``` + var response := client.SomeMethodReturningJSONShownAbove(...) + name := response.fullString[ response.name.offset.utf8 : response.name.offset.utf8 + response.name.length.utf8] +``` + +The service must calculate the offset & length for all 3 encodings and return them because clients find it difficult working with Unicode encodings and how to convert from one encoding to another. In other words, we do this to simplify client development and ensure customer success when isolating a substring. + ## Getting Help: The Azure REST API Stewardship Board The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index dee45afa..b720d29d 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -16,6 +16,7 @@ Please ensure that you add an anchor tag to any new guidelines that you add and | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2024-Jan-17 | Added guidelines on returning string offsets & lengths | | 2023-May-12 | Explain service response for missing/unsupported `api-version` | | 2023-Apr-21 | Update/clarify guidelines on POST method repeatability | | 2023-Apr-07 | Update/clarify guidelines on polymorphism | @@ -438,7 +439,7 @@ This indicates to client libraries and customers that values of the enumeration Polymorphism types in REST APIs refers to the possibility to use the same property of a request or response to have similar but different shapes. This is commonly expressed as a `oneOf` in JsonSchema or OpenAPI. In order to simplify how to determine which specific type a given request or response payload corresponds to, Azure requires the use of an explicit discriminator field. -Note: Polymorphic types can make your service more difficult for nominally typed languages to consume. See the corresponding section in the [Considerations for service design](./ConsiderationsForServiceDesign.md#avoid-surprises) for more information. +Note: Polymorphic types can make your service more difficult for nominally typed languages to consume. See the corresponding section in the [Considerations for service design](./ConsiderationsForServiceDesign.md#avoid-surprises) for more information. :white_check_mark: **DO** define a discriminator field indicating the kind of the resource and include any kind-specific fields in the body. @@ -838,7 +839,7 @@ For example: ### Repeatability of requests Fault tolerant applications require that clients retry requests for which they never got a response, and services must handle these retried requests idempotently. In Azure, all HTTP operations are naturally idempotent except for POST used to create a resource and [POST when used to invoke an action]( -https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#performing-an-action). +https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#performing-an-action). :ballot_box_with_check: **YOU SHOULD** support repeatable requests as defined in [OASIS Repeatable Requests Version 1.0](https://docs.oasis-open.org/odata/repeatable-requests/v1.0/repeatable-requests-v1.0.html) for POST operations to make them retriable. - The tracked time window (difference between the `Repeatability-First-Sent` value and the current time) **MUST** be at least 5 minutes. @@ -1098,6 +1099,14 @@ While it may be tempting to use a revision/version number for the resource as th :white_check_mark: **DO**, when supporting multiple representations (e.g. Content-Encodings) for the same resource, generate different ETag values for the different representations. + +### Returning String Offsets & Lengths (Substrings) + +All string values in JSON are inherently Unicode and UTF-8 encoded, but clients written in a high-level programming language must work with strings in that language's string encoding, which may be UTF-8, UTF-16, or CodePoints (UTF-32). +When a service response includes a string offset or length value, it should specify these values in all 3 encodings to simplify client development and ensure customer success when isolating a substring. + +:white_check_mark: **DO** include all 3 encodings (UTF-8, UTF-16, and CodePoint) for every string offset or length value in a service response. + ### Distributed Tracing & Telemetry Azure SDK client guidelines specify that client libraries must send telemetry data through the `User-Agent` header, `X-MS-UserAgent` header, and Open Telemetry. From e465ea1a89c715ee5f5b4ca1fd8543503b6db294 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 4 Feb 2024 06:32:05 -0600 Subject: [PATCH 662/729] Address PR review feedback --- azure/ConsiderationsForServiceDesign.md | 19 ++++++++++--------- azure/Guidelines.md | 8 ++++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index c219c1e4..ae1e4213 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -6,8 +6,9 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2024-Jan-17 | Added guidelines on returning string offsets & lengths | | 2022-Jul-15 | Update guidance on long-running operations | -| 2022-Feb-01 | Updated error guidance | +| 2022-Feb-01 | Updated error guidance | | 2021-Sep-11 | Add long-running operations guidance | | 2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. | @@ -517,7 +518,7 @@ By computing and returning ETags for your resources, you enable clients to avoid ## Returning String Offsets & Lengths (Substrings) -Some Azure services return substring offset & length values within a string. For example, the offset & length within a string to a name, email address, or phone #. +Some Azure services return substring offset & length values within a string. For example, the offset & length within a string to a name, email address, or phone number. When a service response includes a string, the client's programming language deserializes that string into that language's internal string encoding. Below are the possible encodings and examples of languages that use each encoding: | Encoding | Example languages | @@ -526,11 +527,11 @@ When a service response includes a string, the client's programming language des | UTF-16 | JavaScript, Java, C# | | CodePoint (UTF-32) | Python | -Because the service doesn't know what language a client is written in and what string encoding that language uses, the service can't return UTF-agnostic offset and length values that the client can use to index within the string. To address this, the service response must include offset & length values for all 3 possible encodings and then the client code must select the encoding it required by its language's internal string encoding. +Because the service doesn't know in what language a client is written and what string encoding that language uses, the service can't return UTF-agnostic offset and length values that the client can use to index within the string. To address this, the service response must include offset & length values for all 3 possible encodings and then the client code must select the encoding required by its language's internal string encoding. For example, if a service response needed to identify offset & length values for "name" and "email" substrings, the JSON response would look like this: -``` +```json { (... other properties not shown...) "fullString": "(...some string containing a name and an email address...)", @@ -538,24 +539,24 @@ For example, if a service response needed to identify offset & length values for "offset": { "utf8": 12, "utf16": 10, -      "codePoint": 4 +      "codePoint": 4    },    "length": {    "uft8": 10,       "utf16": 8, -      "codePoint": 2 +      "codePoint": 2     }   },   "email": {  "offset": {       "utf8": 12,       "utf16": 10, -      "codePoint": 4 +      "codePoint": 4     },     "length": {       "uft8": 10,       "utf16": 8, -      "codePoint": 4 +      "codePoint": 4     }   } } @@ -563,7 +564,7 @@ For example, if a service response needed to identify offset & length values for Then, the Go developer, for example, would get the substring containing the name using code like this: -``` +```go var response := client.SomeMethodReturningJSONShownAbove(...) name := response.fullString[ response.name.offset.utf8 : response.name.offset.utf8 + response.name.length.utf8] ``` diff --git a/azure/Guidelines.md b/azure/Guidelines.md index b720d29d..09ab4cdf 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1107,6 +1107,14 @@ When a service response includes a string offset or length value, it should spec :white_check_mark: **DO** include all 3 encodings (UTF-8, UTF-16, and CodePoint) for every string offset or length value in a service response. +:white_check_mark: **DO** define every string offset or length value in a service response as an object with the following structure: + +| Property | Type | Required | Description | +| ----------- | ------- | :------: | ----------- | +| `utf8` | integer | true | The offset or length of the substring in UTF-8 encoding | +| `utf16` | integer | true | The offset or length of the substring in UTF-16 encoding | +| `codePoint` | integer | true | The offset or length of the substring in CodePoint encoding | + ### Distributed Tracing & Telemetry Azure SDK client guidelines specify that client libraries must send telemetry data through the `User-Agent` header, `X-MS-UserAgent` header, and Open Telemetry. From 713642478bf55e317f9b8cea41ce3165b8e2fc74 Mon Sep 17 00:00:00 2001 From: bart simpson <152961343+bart1024@users.noreply.github.com> Date: Wed, 7 Feb 2024 12:08:09 +0200 Subject: [PATCH 663/729] fixed example in markdown --- graph/articles/collections.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 7347dfc0..6a24d9f0 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -93,6 +93,7 @@ For example, the following request: ```http POST https://graph.microsoft.com/beta/teamwork/devices +``` Would lead to a response indicating the location of the new collection item: From 725055824a5a6dcfc47694a2d54adf46e5ca74b3 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 8 Feb 2024 19:15:45 -0600 Subject: [PATCH 664/729] Update ConsiderationsForServiceDesign.md Fix formatting of json example. --- azure/ConsiderationsForServiceDesign.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index ae1e4213..d1839d3b 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -531,7 +531,7 @@ Because the service doesn't know in what language a client is written and what s For example, if a service response needed to identify offset & length values for "name" and "email" substrings, the JSON response would look like this: -```json +```text { (... other properties not shown...) "fullString": "(...some string containing a name and an email address...)", From 194adeb29bdb958712ae89f863aed35d8469df21 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 9 Feb 2024 06:54:38 -0600 Subject: [PATCH 665/729] Add reference to String Offsets & Lengths topic in Considerations for Service Design This PR adds a reference in the guidelines for String Offsets & Lengths to the corresponding section in Considerations for Service Design, which has more details and an example JSON response value. --- azure/Guidelines.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 09ab4cdf..c8fa7d4a 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1104,6 +1104,9 @@ While it may be tempting to use a revision/version number for the resource as th All string values in JSON are inherently Unicode and UTF-8 encoded, but clients written in a high-level programming language must work with strings in that language's string encoding, which may be UTF-8, UTF-16, or CodePoints (UTF-32). When a service response includes a string offset or length value, it should specify these values in all 3 encodings to simplify client development and ensure customer success when isolating a substring. +See the [Returning String Offsets & Lengths] section in Considerations for Service Design for more detail, including an example JSON response containing string offset and length fields. + +[Returning String Offsets & Lengths]: https://github.com/microsoft/api-guidelines/blob/vNext/azure/ConsiderationsForServiceDesign.md#returning-string-offsets--lengths-substrings :white_check_mark: **DO** include all 3 encodings (UTF-8, UTF-16, and CodePoint) for every string offset or length value in a service response. From 6066d210ed6319132c12891dda604b228abd97ee Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 26 Feb 2024 18:43:34 -0600 Subject: [PATCH 666/729] Fix links to ARM RPC repo --- azure/ConsiderationsForServiceDesign.md | 2 +- azure/Guidelines.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index d1839d3b..c9da5f42 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -34,7 +34,7 @@ _Note: Developing a new service requires the development of at least 1 (manageme A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used to provision and control the operational state of resources. A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful for provisioning/control and applications. In this case, the operation can appear in both APIs. -Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on [the conventions of the Azure ARM](https://github.com/Azure/azure-resource-manager-rpc). +Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on [the conventions of the Azure ARM](https://github.com/cloud-and-ai-microsoft/resource-provider-contract). ## Start with the Developer Experience diff --git a/azure/Guidelines.md b/azure/Guidelines.md index c8fa7d4a..70c99a43 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -45,7 +45,7 @@ These guidelines apply to Azure service teams implementing _data plane_ APIs. Th 2. Customers can adopt a version without requiring code changes Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. [Open an issue](https://github.com/microsoft/api-guidelines/issues/new/choose) to suggest a change or propose a new idea. Please read the [Considerations for Service Design](./ConsiderationsForServiceDesign.md) for an introduction to the topic of API design for Azure services. *For an existing GA'd service, don't change/break its existing API; instead, leverage these concepts for future APIs while prioritizing consistency within your existing service.* -*Note: If you are creating a management plane (ARM) API, please refer to the [Azure Resource Manager Resource Provider Contract](https://github.com/Azure/azure-resource-manager-rpc).* +*Note: If you are creating a management plane (ARM) API, please refer to the [Azure Resource Manager Resource Provider Contract](https://github.com/cloud-and-ai-microsoft/resource-provider-contract).* ### Prescriptive Guidance This document offers prescriptive guidance labeled as follows: From 74828385381eed0597ca7bb5883dc30520513a38 Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Mon, 11 Mar 2024 19:39:49 -0700 Subject: [PATCH 667/729] Clarify that casting is not required in order to see default properties of derived types. Also make examples more consistent and complete. --- graph/patterns/subtypes.md | 88 ++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/graph/patterns/subtypes.md b/graph/patterns/subtypes.md index c5c6a098..aad10593 100644 --- a/graph/patterns/subtypes.md +++ b/graph/patterns/subtypes.md @@ -2,7 +2,7 @@ Microsoft Graph API Design Pattern -*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This lets us model collections of resources that have slightly different metadata and behavior.* +*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This lets us model collections of resources that have slightly different properties and behavior.* ## Problem @@ -27,9 +27,9 @@ You can consider related patterns such as [facets](./facets.md) and [flat bag of ## Issues and considerations When introducing a new subtype to the hierarchy, developers need to ensure that -the new subtype doesn't change the semantic of the type hierarchy with its implicit constraints. +the new subtype doesn't change the semantic of the type hierarchy or collections of the specified base type with implicit constraints. -To retrieve properties specific for a derived type, an API request URL might need to include casting to the derived type. If the type hierarchy is very deep, then the resulting URL might become very long and not easily readable. +To reference properties specific to a derived type, an API request URL might need to include a segment casting to the derived type. If the type hierarchy is very deep, then the resulting URL might become very long and not easily readable. There are a few considerations to take into account when new subtypes are introduced: @@ -52,65 +52,99 @@ and groups stored in Azure Active Directory. Because any directoryObject object
    - - + + ``` Groups and users are derived types and modeled as follows: ```XML -  -        + + ... -        -… + + ... ``` An API request to get members of a group returns a heterogeneous collection of users and groups where each element can be a user or a group, and has an -additional property @odata.type for a variant subtype: +additional `@odata.type` property that specifies the subtype: ``` -GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members?$select=id,displayName +GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members -Response payload shortened for readability: +Response payload shortened for readability. The deletedDateTime property from the base type is a non-default property and is only returned if explicitly requested. { "@odata.context": -"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects", -    "value": [ -        {           +"https://graph.microsoft.com/v1.0/$metadata#directoryObjects", + "value": [ + { "@odata.type": "#microsoft.graph.user", "id": "37ca648a-a007-4eef-81d7-1127d9be34e8", - "displayName": "John Cob" + "jobTitle": "CEO", + ... }, { "@odata.type": "#microsoft.graph.group", "id": "45f25951-d04f-4c44-b9b0-2a79e915658d", - "displayName": "Department 456" + "description": "Microsoft Graph API Reviewers", + ... }, ... -    ] + ] +} +``` + +Addressing a property of the subtype, for example, in `$filter` or `$select`, requires prefixing the property with the fully-qualified name of the subtype (or type derived from the subtype) on which it is defined. To filter on the `jobTitle` for the user type, you need to qualify the property with `microsoft.graph.user`. + +The following query returns all groups that are members of group a94a666e-0367-412e-b96e-54d28b73b2db, as well as users that are members and whose jobTitle is CEO. + +``` +GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members?$filter=microsoft.graph.user/jobTitle eq 'CEO' + +Response payload shortened for readability: + +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#directoryObjects", + "value": [ + { + "@odata.type": "#microsoft.graph.user", + "id": "37ca648a-a007-4eef-81d7-1127d9be34e8", + "jobTitle": "CEO", + ... + }, + { + "@odata.type": "#microsoft.graph.group", + "id": "45f25951-d04f-4c44-b9b0-2a79e915658d", + "description": "Microsoft Graph API Reviewers", + ... + }, + ... + ] } ``` -An API request for a subtype-specific property requires type casting to the subtype; that is, to retrieve the jobTitle property enabled for the user type, you need to cast from the directoryObject collection items to the `microsoft.graph.group` derived type. +An entire collection can be cast to a particular subtype by appending the fully-qualified subtype name to the URL. Doing so filters the collection to members of (or derived from) that particular subtype, and makes the properties of that subtype available without casting. In this case, the `@odata.type` attribute is not returns for records of the specified subtype because the `@odata.context` indicates that the entire collection is consists of the particular subtype. Types derived from that subtype do still have the `@odata.type` attribute. + +The following query returns only users that are members of group a94a666e-0367-412e-b96e-54d28b73b2db and whose jobTitle is CEO. ``` -GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members/microsoft.graph.user?$select=displayName,jobTitle +GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members/microsoft.graph.user?$filter=jobTitle eq 'CEO' Response payload shortened for readability: { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(displayName,jobTitle)", + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users", "value": [ { - "displayName": "John Cob", - "jobTitle": "RESEARCHER II" + "id": "37ca648a-a007-4eef-81d7-1127d9be34e8", + "jobTitle": "CEO", + ... }, ... ] @@ -124,9 +158,7 @@ POST https://graph.microsoft.com/v1.0/directoryObjects { "@odata.type": "#microsoft.graph.group", - "displayName": "Library Assist", - "mailEnabled": false, - "mailNickname": "library", - "securityEnabled": true + "description": "Microsoft Graph API Reviewers", + ... } ``` From 3fdf4261ec1d785ba78a3479f66d98d10e1933ad Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 21 Jan 2024 19:19:05 -0600 Subject: [PATCH 668/729] Add links to all guidelines in the Considerations for Service Design doc. --- azure/ConsiderationsForServiceDesign.md | 67 +++++++++++++------------ azure/Guidelines.md | 2 +- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index c9da5f42..5738505c 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -1,6 +1,7 @@ # Considerations for Service Design - + + ## History @@ -23,11 +24,11 @@ Azure Service teams should engage the Azure HTTP/REST Stewardship Board early in It is critically important to design your service to avoid disrupting users as the API evolves: -:white_check_mark: **DO** implement API versioning starting with the very first release of the service. +:white_check_mark: **DO** implement API versioning starting with the very first release of the service. -:white_check_mark: **DO** ensure that customer workloads never break +:white_check_mark: **DO** ensure that customer workloads never break -:white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library **without requiring code changes** +:white_check_mark: **DO** ensure that customers are able to adopt a new version of service or SDK client library **without requiring code changes** ## Azure Management Plane vs Data Plane _Note: Developing a new service requires the development of at least 1 (management plane) API and potentially one or more additional (data plane) APIs. When reviewing v1 service APIs, we see common advice provided during the review._ @@ -63,27 +64,27 @@ _For this reason, it is **much better** to ship with fewer features and only add Focusing on hero scenarios reduces development, support, and maintenance costs; enables teams to align and reach consensus faster; and accelerates the time to delivery. A telltale sign of a service that has not focused on hero scenarios is "API drift," where endpoints are inconsistent, incomplete, or juxtaposed to one another. -:white_check_mark: **DO** define "hero scenarios" first including abstractions, naming, relationships, and then define the API describing the operations required +:white_check_mark: **DO** define "hero scenarios" first including abstractions, naming, relationships, and then define the API describing the operations required. -:white_check_mark: **DO** provide example code demonstrating the "Hero Scenarios" +:white_check_mark: **DO** provide example code demonstrating the "Hero Scenarios". -:white_check_mark: **DO** consider how your abstractions will be represented in different high-level languages. +:white_check_mark: **DO** consider how your abstractions will be represented in different high-level languages. -:white_check_mark: **DO** develop code examples in at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#) to illustrate your abstractions and high-level language representations. +:white_check_mark: **DO** develop code examples in at least one dynamically typed language (for example, Python or JavaScript) and one statically typed language (for example, Java or C#) to illustrate your abstractions and high-level language representations. -:no_entry: **DO NOT** proactively add APIs for speculative features customers might want +:no_entry: **DO NOT** proactively add APIs for speculative features customers might want. ### Start with your API Definition Understanding how your service is used and defining its model and interaction patterns--its API--should be one of the earliest activities a service team undertakes. It reflects the abstractions & naming decisions and makes it easy for developers to implement the hero scenarios. -:white_check_mark: **DO** create an [OpenAPI Definition](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) describing the service. The OpenAPI definition is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of services. +:white_check_mark: **DO** create an [OpenAPI description](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (with [autorest extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md)) for the service API. The OpenAPI description is a key element of the Azure SDK plan and is essential for documentation, usability and discoverability of service APIs. ## Design for Change Resiliency As you build out your service and API, there are a number of decisions that can be made up front that add resiliency to client implementations. Addressing these as early as possible will help you iterate faster and avoid breaking changes. -:ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. +:ballot_box_with_check: **YOU SHOULD** use extensible enumerations. Extensible enumerations are modeled as strings - expanding an extensible enumeration is not a breaking change. -:ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. +:ballot_box_with_check: **YOU SHOULD** implement [conditional requests](https://tools.ietf.org/html/rfc7232) early. This allows you to support concurrency, which tends to be a concern later on. ## Use Good Names @@ -99,43 +100,43 @@ Use common patterns and standard conventions to aid developers in correctly gues Use verbose naming patterns and avoid abbreviations other than well-known acronyms in your service domain. -:heavy_check_mark: **DO** use the same name for the same concept and different names for different concepts wherever possible. +:white_check_mark: **DO** use the same name for the same concept and different names for different concepts wherever possible. ### Recommended Naming Conventions The following are recommended naming conventions for Azure services: -:heavy_check_mark: **DO** name collections as plural nouns or plural noun phrases using correct English. +:white_check_mark: **DO** name collections as plural nouns or plural noun phrases using correct English. -:heavy_check_mark: **DO** name values that are not collections as singular nouns or singular noun phrases. +:white_check_mark: **DO** name values that are not collections as singular nouns or singular noun phrases. -:ballot_box_with_check: **YOU SHOULD** should place the adjective before the noun in names that contain both a noun and an adjective. +:ballot_box_with_check: **YOU SHOULD** should place the adjective before the noun in names that contain both a noun and an adjective. For example, `collectedItems` not `itemsCollected` -:ballot_box_with_check: **YOU SHOULD** case all acronyms as though they were regular words (i.e. lower camelCase). +:ballot_box_with_check: **YOU SHOULD** case all acronyms as though they were regular words (i.e. lower camelCase). For example, `nextUrl` not `nextURL`. -:ballot_box_with_check: **YOU SHOULD** use an "At" suffix in names of `date-time` values. +:ballot_box_with_check: **YOU SHOULD** use an "At" suffix in names of `date-time` values. For example, `createdAt` not `created` or `createdDateTime`. -:ballot_box_with_check: **YOU SHOULD** use a suffix of the unit of measurement for values with a clear unit of measurement (such as bytes, miles, and so on). Use a generally accepted abbreviation for the units (e.g. "Km" rather than "Kilometers") when appropriate. +:ballot_box_with_check: **YOU SHOULD** use a suffix of the unit of measurement for values with a clear unit of measurement (such as bytes, miles, and so on). Use a generally accepted abbreviation for the units (e.g. "Km" rather than "Kilometers") when appropriate. -:ballot_box_with_check: **YOU SHOULD** use an int for time durations and include the time units in the name. +:ballot_box_with_check: **YOU SHOULD** use an int for time durations and include the time units in the name. For example, `expirationDays` as `int` and not `expiration` as `date-time`. -:warning: **YOU SHOULD NOT** use brand names in resource or property names. +:warning: **YOU SHOULD NOT** use brand names in resource or property names. -:warning: **YOU SHOULD NOT** use acronyms or abbreviations unless they are broadly understood for example, "ID" or "URL", but not "Num" for "number". +:warning: **YOU SHOULD NOT** use acronyms or abbreviations unless they are broadly understood for example, "ID" or "URL", but not "Num" for "number". -:warning: **YOU SHOULD NOT** use names that are reserved words in widely used programming languages (including C#, Java, JavaScript/TypeScript, Python, C++, and Go). +:warning: **YOU SHOULD NOT** use names that are reserved words in widely used programming languages (including C#, Java, JavaScript/TypeScript, Python, C++, and Go). -:no_entry: **DO NOT** use "is" prefix in names of `boolean` values, e.g. "enabled" not "isEnabled". +:no_entry: **DO NOT** use "is" prefix in names of `boolean` values, e.g. "enabled" not "isEnabled". -:no_entry: **DO NOT** use redundant words in names. +:no_entry: **DO NOT** use redundant words in names. For example, `/phones/number` and not `phone/phoneNumber`. @@ -153,22 +154,22 @@ The following are recommended names for properties that match the associated des ### `name` vs `id` -:heavy_check_mark: **DO** use "Id" suffix for the name of the identifier of a resource. +:white_check_mark: **DO** use "Id" suffix for the name of the identifier of a resource. This holds even in the case where the identifier is assigned by the user with a PUT/PATCH method. ## Use Previews to Iterate Before releasing your API plan to invest significant design effort, get customer feedback, & iterate through multiple preview releases. This is especially important for V1 as it establishes the abstractions and patterns that developers will use to interact with your service. -:ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. +:ballot_box_with_check: **YOU SHOULD** write and test hypotheses about how your customers will use the API. -:ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. +:ballot_box_with_check: **YOU SHOULD** release and evaluate a minimum of 2 preview versions prior to the first GA release. -:ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. +:ballot_box_with_check: **YOU SHOULD** identify key scenarios or design decisions in your API that you want to test with customers, and ask customers for feedback and to share relevant code samples. -:ballot_box_with_check: **YOU SHOULD** consider doing a _code with_ exercise in which you actively develop with the customer, observing and learning from their API usage. +:ballot_box_with_check: **YOU SHOULD** consider doing a _code with_ exercise in which you actively develop with the customer, observing and learning from their API usage. -:ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. +:ballot_box_with_check: **YOU SHOULD** capture what you have learned during the preview stage and share these findings with your team and with the API Stewardship Board. ## Communicate Deprecations As your service evolves over time, it will be natural that you want to remove operations that are no longer needed. For example, additional requirements or new capability in your service, may have resulted in a new operation that, effectively, replaces an old one. @@ -187,12 +188,12 @@ Collections are another common area of friction for developers. It is important An important consideration when defining a new service is support for pagination. -:ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. See [Collections](./Guidelines.md#collections) for specific guidance. +:ballot_box_with_check: **YOU SHOULD** support server-side paging, even if your resource does not currently need paging. This avoids a breaking change when your service expands. See [Collections](./Guidelines.md#collections) for specific guidance. Another consideration for collections is support for sorting the set of returned items with the _orderby_ query parameter. Sorting collection results can be extremely expensive for a service to implement as it must retrieve all items to sort them. And if the operation supports paging (which is likely), then a client request to get another page may have to retrieve all items and sort them again to determine which items are on the desired page. -:heavy_check_mark: **YOU MAY** support `orderby` if customer scenarios really demand it and the service is confident that it can support it in perpetuity (even if the backing storage service changes someday). +:heavy_check_mark: **YOU MAY** support `orderby` if customer scenarios really demand it and the service is confident that it can support it in perpetuity (even if the backing storage service changes someday). Another important design pattern for avoiding surprises is idempotency. An operation is idempotent if it can be performed multiple times and have the same result as a single execution. HTTP requires certain operations like GET, PUT, and DELETE to be idempotent, but for cloud services it is important to make _all_ operations idempotent so that clients can use retry in failure scenarios without risk of unintended consequences. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 70c99a43..d981a729 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -802,7 +802,7 @@ While removing a value from an enum is a breaking change, adding value to an enu } ``` -:ballot_box_with_check: **You SHOULD** use extensible enums unless you are positive that the symbol set will **NEVER** change over time. +:ballot_box_with_check: **YOU SHOULD** use extensible enums unless you are positive that the symbol set will **NEVER** change over time. ### Deprecating Behavior Notification From 9fbcaa5f0d46c9be6085e425cb1d9f40199225e3 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 20 Mar 2024 09:23:05 -0700 Subject: [PATCH 669/729] Update dictionary.md --- graph/patterns/dictionary.md | 52 +++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 125d87ce..25633393 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -41,11 +41,13 @@ For more information, see the [OData reference](https://github.com/oasis-tcs/oda The following example demonstrates defining a dictionary that can contain string values. ```xml - + - + + + Edm.String @@ -64,7 +66,7 @@ The following example shows defining a dictionary property, "userTags", on the i ```xml ... - + ``` @@ -117,25 +119,33 @@ Dictionaries can also contain complex types whose values may be constrained to a The following example defines a complex type **roleSettings**, an **assignedRoleGroupDictionary** that contains **roleSettings**, and an **assignedRoles** property that uses the dictionary.. ```xml - - ... - - - - - - - - - - - microsoft.graph.roleSettings - - - + + + + + + + + ... + + + + + + + + + + + + microsoft.graph.roleSettings + + + + ``` ### Reading a entity with a complex-typed dictionary From a50a52c66dfd26fa85e96840856aeed25b781851 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 20 Mar 2024 09:24:41 -0700 Subject: [PATCH 670/729] Update dictionary.md --- graph/patterns/dictionary.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index 25633393..e9dbf1b9 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -41,7 +41,7 @@ For more information, see the [OData reference](https://github.com/oasis-tcs/oda The following example demonstrates defining a dictionary that can contain string values. ```xml - + @@ -119,8 +119,7 @@ Dictionaries can also contain complex types whose values may be constrained to a The following example defines a complex type **roleSettings**, an **assignedRoleGroupDictionary** that contains **roleSettings**, and an **assignedRoles** property that uses the dictionary.. ```xml - - + From ab0700fcc2129478c7a8a3b14d6ca8ef088199a8 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 20 Mar 2024 09:26:28 -0700 Subject: [PATCH 671/729] Update dictionary.md --- graph/patterns/dictionary.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index e9dbf1b9..e00e33e1 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -37,7 +37,9 @@ For more information, see the [OData reference](https://github.com/oasis-tcs/oda ## Examples -### Declaring a string dictionary +### String dictionary + +#### CSDL declaration The following example demonstrates defining a dictionary that can contain string values. ```xml @@ -60,7 +62,7 @@ The following example demonstrates defining a dictionary that can contain string Please note that schema validation will fail due to the casing of `Dictionary`. This warning should be suppressed. -### Defining a dictionary property +#### Defining a dictionary property The following example shows defining a dictionary property, "userTags", on the item entity type. ```xml @@ -70,7 +72,7 @@ The following example shows defining a dictionary property, "userTags", on the i ``` -### Reading a dictionary +#### Reading a dictionary Dictionaries are represented in JSON payloads as a JSON object, where the property names are comprised of the keys and their values are the corresponding key values. The following example shows reading an item with a dictionary property named "userTags": @@ -90,7 +92,7 @@ Response: } ``` -### Setting a dictionary value +#### Setting a dictionary value The following example shows setting a dictionary value. If "hairColor" already exists, it is updated, otherwise it is added. ```http @@ -102,7 +104,7 @@ PATCH /item/userTags } ``` -### Deleting a dictionary value +#### Deleting a dictionary value A dictionary value can be removed by setting the value to null. ```http PATCH /item/userTags @@ -113,7 +115,9 @@ PATCH /item/userTags } ``` -### Declaring a complex typed dictionary +### Complex typed dictionary + +#### CSDL declaration Dictionaries can also contain complex types whose values may be constrained to a particular set of complex types. The following example defines a complex type **roleSettings**, an **assignedRoleGroupDictionary** that contains **roleSettings**, and an **assignedRoles** property that uses the dictionary.. @@ -147,7 +151,7 @@ The following example defines a complex type **roleSettings**, an **assignedRole ``` -### Reading a entity with a complex-typed dictionary +#### Reading a entity with a complex-typed dictionary The following example illustrates reading an entity containing the complex-typed dictionary "assignedRoles". @@ -175,7 +179,7 @@ Response: } ``` -### Reading the dictionary property +#### Reading the dictionary property The following example shows getting just the "assignedRoles" dictionary property. ```HTTP From ddeed9f0e405b88cb98f0093e13693b93baf5f13 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Thu, 21 Mar 2024 13:42:17 -0400 Subject: [PATCH 672/729] Update graph/patterns/dictionary.md Co-authored-by: Michael Pizzo --- graph/patterns/dictionary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/dictionary.md b/graph/patterns/dictionary.md index e00e33e1..234aebc3 100644 --- a/graph/patterns/dictionary.md +++ b/graph/patterns/dictionary.md @@ -139,7 +139,7 @@ The following example defines a complex type **roleSettings**, an **assignedRole - of roleSettings keyed by name of roleGroup. --> From 5f97b29d9b68dbad1c96ccce76e7b1d398a58296 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:43:06 -0400 Subject: [PATCH 673/729] Update graph/patterns/navigation-property.md Co-authored-by: Dan Kershaw [MSFT] --- graph/patterns/navigation-property.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/navigation-property.md b/graph/patterns/navigation-property.md index 338e5210..6551e2a0 100644 --- a/graph/patterns/navigation-property.md +++ b/graph/patterns/navigation-property.md @@ -41,7 +41,7 @@ Navigation properties defined within an entity are not returned by default when Implementing support for accessing the "$ref" of a navigation property allows a caller to return just the URL of related resource. e.g. `/user/23/manager/$ref`. This is useful when a client wishes to identify the related resource but doesn't need all of its properties. -The strongly-typed nature of navigation properties is valuable for backend services and for client applications. +The strongly-typed nature of navigation properties is valuable for backend services and for client applications, when compared with the weakly-typed foreign key property. Strong typing allows some documentation and visualizations to be automatically generated, it allows SDK generation, and it allows some automated client code generation; it also prevents the need to store duplicate data on the service side and as a result has improved data consistency across APIs since the duplicate data does not need to be regularly refreshed. ## When to Use this Pattern From 55e2ef054403321ae25dac2f3980d2e5610c7361 Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:54:05 -0700 Subject: [PATCH 674/729] Create coreTypes.md --- graph/articles/coreTypes.md | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 graph/articles/coreTypes.md diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md new file mode 100644 index 00000000..978e5e88 --- /dev/null +++ b/graph/articles/coreTypes.md @@ -0,0 +1,56 @@ +# Core Types + +## Overview + +Types exist in graph which are highly-connected/central to the graph ecosystem. Often, these types are the position of containing many structural properties relevant to other APIs, because they are connected to many entities in the graph. + +Structural properties should be only added to these core types when they are properties of the entity itself and strictly not for the purpose of convenience due to the entity's position in the graph. + +## Core Types in Graph + +The following types are identified as core types, and will require strong justification to allow new structural properties to be added in all cases. + +- ```user``` +- ```group``` +- ```device``` + +## Alternatives to Adding Structural Properties + +Instead of adding a structural property to an existing type, do the following: +1. Create a new type that models the information captured in the proposed structural property. +2. Create a navigation property linking the new type to the existing type, doing one of the following: + - Contain the new type in an entity set elsewhere, and add a navigation property to the existing type on the new type. + - Contain the new type in an entity set elsewhere, and add a navigation property to the new type on the existing type. + - Add a navigation property to the new type on the existing type, containing the new type. + +## Example: + +Modeling adding "bank account information", which includes two properties `accountNumber` and `routingNumber`, to entity type ```user```. + +### Don't: + +Don't add new properties to core types such as `user`. + +```xml + + + + +``` + +### Do: + +Instead, create a new entity modeling the concept that you wish to introduce, and create a navigation property connecting that entity to the desired type. + +```xml + + + + + +... + + + + +``` From 57e6d711b03d6e497f24bfe6dc75484c4b2a33ed Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:25:27 -0700 Subject: [PATCH 675/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index abfd577c..8227e4ad 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -14,6 +14,7 @@ Table of contents - [Query support](#query-support) - [Behavior modeling](#behavior-modeling) - [Error handling](#error-handling) + - [Limitations on core types](#limitations-on-core-types) - [External standards](#external-standards) - [API contract and nonbackward compatible changes](#api-contract-and-nonbackward-compatible-changes) - [Versioning and deprecation](#versioning-and-deprecation) @@ -332,6 +333,15 @@ For a complete mapping of error codes to HTTP statuses, see +### Limitations on core types + +The types `user`, `group`, and `device` should not have any new structural property(s) added, without compelling justification. +Instead, model the concept represented in those property(s) as a new entity, and do one of the following: +1. Add navigation to the new entity from `user`, `group`, or `device`. +2. Contain the new entity elsewhere, and add a navigation from the new entity to `user`, `group` or `device`. + +More details and examples are available in [Core types](./articles/coreTypes.md). + ## External standards For ease of client use and interoperatibility, some APIs might implement a standard that is defined external to Microsoft Graph and OData. From 5818436c86d94f77d756a7d2fc0764b8bd1ed794 Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:30:03 -0700 Subject: [PATCH 676/729] Update coreTypes.md --- graph/articles/coreTypes.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index 978e5e88..f23cc895 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -16,12 +16,11 @@ The following types are identified as core types, and will require strong justif ## Alternatives to Adding Structural Properties -Instead of adding a structural property to an existing type, do the following: -1. Create a new type that models the information captured in the proposed structural property. -2. Create a navigation property linking the new type to the existing type, doing one of the following: - - Contain the new type in an entity set elsewhere, and add a navigation property to the existing type on the new type. - - Contain the new type in an entity set elsewhere, and add a navigation property to the new type on the existing type. - - Add a navigation property to the new type on the existing type, containing the new type. +Instead of adding a structural property to the existing type (`user`, `group` or `device`), create a new type that models the information captured in the proposed structural property(s). +Then, do one of the following: +- Add a navigation property on the existing type to the new type, containing the new type. +- Contain the new type in an entity set elsewhere, and add a navigation property to the existing type on the new type. +- Contain the new type in an entity set elsewhere, and add a navigation property to the new type on the existing type. ## Example: @@ -40,16 +39,18 @@ Don't add new properties to core types such as `user`. ### Do: -Instead, create a new entity modeling the concept that you wish to introduce, and create a navigation property connecting that entity to the desired type. +First, create a new type that models the information captured in the desired structural property(s). ```xml +``` -... +Then, for example, add a navigation property on the existing type, containing the new type: +```xml From cc8a93406eac53446ea3daba5ff3cf7afa36bd08 Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:26:07 -0700 Subject: [PATCH 677/729] Update coreTypes.md --- graph/articles/coreTypes.md | 48 +++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index f23cc895..6d070c6a 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -19,8 +19,8 @@ The following types are identified as core types, and will require strong justif Instead of adding a structural property to the existing type (`user`, `group` or `device`), create a new type that models the information captured in the proposed structural property(s). Then, do one of the following: - Add a navigation property on the existing type to the new type, containing the new type. -- Contain the new type in an entity set elsewhere, and add a navigation property to the existing type on the new type. - Contain the new type in an entity set elsewhere, and add a navigation property to the new type on the existing type. +- Contain the new type in an entity set elsewhere, and add a navigation property to the existing type on the new type. ## Example: @@ -39,8 +39,11 @@ Don't add new properties to core types such as `user`. ### Do: -First, create a new type that models the information captured in the desired structural property(s). +Do one of the following: +#### Add a navigation property on the existing type to the new type, containing the new type. + +Define the new entity type: ```xml @@ -48,10 +51,47 @@ First, create a new type that models the information captured in the desired str ``` -Then, for example, add a navigation property on the existing type, containing the new type: - +Add a contained navigation from user to the new entity type: ```xml ``` + +#### Contain the new type in an entity set elsewhere, and add a navigation property to the new type on the existing type. + +Define the new entity type: +```xml + + + + +``` + +Contain the new entity type in an entity set or singleton: +```xml + +``` + +Add a navigation from user to the new type: +```xml + + + +``` + +#### Contain the new type in an entity set elsewhere, and add a navigation property to the existing type on the new type. + +Define the new entity type, with a navigation to the user: +```xml + + + + + +``` + +Contain the new entity type in an entity set or singleton: +```xml + +``` From 2ca37ffc020c6e305fa7f35d8106df42a477c9a0 Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:27:52 -0700 Subject: [PATCH 678/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 8227e4ad..fc488762 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -337,8 +337,8 @@ For a complete mapping of error codes to HTTP statuses, see The types `user`, `group`, and `device` should not have any new structural property(s) added, without compelling justification. Instead, model the concept represented in those property(s) as a new entity, and do one of the following: -1. Add navigation to the new entity from `user`, `group`, or `device`. -2. Contain the new entity elsewhere, and add a navigation from the new entity to `user`, `group` or `device`. +1. Add navigation from `user`, `group`, or `device` to the new entity. +2. Add a navigation from the new entity to `user`, `group` or `device`. More details and examples are available in [Core types](./articles/coreTypes.md). From 6aa47c176abf2cebb9836460b8460027c048dac7 Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:23:58 -0700 Subject: [PATCH 679/729] Update graph/articles/coreTypes.md Co-authored-by: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> --- graph/articles/coreTypes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index 6d070c6a..d2efafc6 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -2,7 +2,7 @@ ## Overview -Types exist in graph which are highly-connected/central to the graph ecosystem. Often, these types are the position of containing many structural properties relevant to other APIs, because they are connected to many entities in the graph. +Types exist in graph which are highly-connected/central to the graph ecosystem. Often, these types are in the position of containing many structural properties relevant to other APIs, because they are connected to many entities in the graph. Structural properties should be only added to these core types when they are properties of the entity itself and strictly not for the purpose of convenience due to the entity's position in the graph. From c9405c47f9e29e136c25e512f6ab3e86d3079f5f Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:24:07 -0700 Subject: [PATCH 680/729] Update graph/articles/coreTypes.md Co-authored-by: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> --- graph/articles/coreTypes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index d2efafc6..e4612c81 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -41,7 +41,7 @@ Don't add new properties to core types such as `user`. Do one of the following: -#### Add a navigation property on the existing type to the new type, containing the new type. +#### Option 1: Add a navigation property on the existing type to the new type, containing the new type. Define the new entity type: ```xml From 34b91925d2ecbcf9024e0b374f2fe651b8887711 Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:24:13 -0700 Subject: [PATCH 681/729] Update graph/articles/coreTypes.md Co-authored-by: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> --- graph/articles/coreTypes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index e4612c81..2fdd3ae9 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -58,7 +58,7 @@ Add a contained navigation from user to the new entity type: ``` -#### Contain the new type in an entity set elsewhere, and add a navigation property to the new type on the existing type. +#### Option 2: Contain the new type in an entity set elsewhere, and add a navigation property to the new type on the existing type. Define the new entity type: ```xml From af4e80ac54b378557087ab99c9fd34690116211f Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:24:18 -0700 Subject: [PATCH 682/729] Update graph/articles/coreTypes.md Co-authored-by: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> --- graph/articles/coreTypes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index 2fdd3ae9..c41bfdda 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -80,7 +80,7 @@ Add a navigation from user to the new type: ``` -#### Contain the new type in an entity set elsewhere, and add a navigation property to the existing type on the new type. +#### Option 3: Contain the new type in an entity set elsewhere, and add a navigation property to the existing type on the new type. Define the new entity type, with a navigation to the user: ```xml From c7d483c1878c7a14be1c52c6296ed41d339a82be Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Mon, 6 May 2024 06:15:55 -0700 Subject: [PATCH 683/729] Updated LRO guidelines (#517) * Added LRO guidelines * Restore all prior named guidelines with some edits where needed * Address PR review feedback Co-authored-by: Mike Kistler Co-authored-by: Weidong Xu --- azure/ConsiderationsForServiceDesign.md | 240 +++++++++++++++------- azure/Guidelines.md | 257 +++++++++++++++++++----- 2 files changed, 370 insertions(+), 127 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 5738505c..663b2e16 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -7,6 +7,7 @@ | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2024-Mar-17 | Updated LRO guidelines | | 2024-Jan-17 | Added guidelines on returning string offsets & lengths | | 2022-Jul-15 | Update guidance on long-running operations | | 2022-Feb-01 | Updated error guidance | @@ -208,7 +209,7 @@ It is good practice to define the path for action operations that is easily dist 2) use a special character not in the set of valid characters for resource names to distinguish the "action" in the path. In Azure we recommend distinguishing action operations by appending a ':' followed by an action verb to the final path segment. E.g. -```http +```text https://...//:? ``` @@ -217,7 +218,7 @@ cannot collide with a resource path that contains user-specified resource ids. ## Long-Running Operations -Long-running operations are an API design pattern that should be used when the processing of +Long-running operations (LROs) are an API design pattern that should be used when the processing of an operation may take a significant amount of time -- longer than a client will want to block waiting for the result. @@ -226,16 +227,95 @@ a _status monitor_, which is an ephemeral resource that will track the status an The status monitor resource is distinct from the target resource (if any) and specific to the individual operation request. -A POST or DELETE operation returns a `202 Accepted` response with the status monitor in the response body. -A long-running POST should not be used for resource create -- use PUT as described below. -PATCH must never be used for long-running operations -- it should be reserved for simple resource updates. -If a long-running update is required it should be implemented with POST. +There are four types of LROs allowed in Azure REST APIs: + +1. An LRO to create or replace a resource that involves additional long-running processing. +2. An LRO to delete a resource. +3. An LRO to perform an action on or with an existing resource (or resource collection). +4. An LRO to perform an action not related to an existing resource (or resource collection). + +The following sections describe these patterns in detail. + +### Create or replace a resource requiring additional long-running processing + + +A special case of long-running operations that occurs often is a PUT operation to create or replace a resource +that involves some additional long-running processing. +One example is a resource that requires physical resources (e.g. servers) to be "provisioned" to make the resource functional. + +In this case: +- The operation must use the PUT method (NOTE: PATCH is never allowed here) +- The URL identifies the resource being created or replaced. +- The request and response body have identical schemas & represent the resource. +- The request may contain an `Operation-Id` header that the service will use as +the ID of the status monitor created for the operation. +- If the `Operation-Id` matches an existing operation and the request content is the same, +treat as a retry and return the same response as the earlier request. +Otherwise fail the request with a `409-Conflict`. + +```text +PUT /items/FooBar&api-version=2022-05-01 +Operation-Id: 22 + +{ + "prop1": 555, + "prop2": "something" +} +``` -There is a special form of long-running operation initiated with PUT that is described -in [Create (PUT) with additional long-running processing](./Guidelines.md#put-operation-with-additional-long-running-processing). -The remainder of this section describes the pattern for long-running POST and DELETE operations. +In this case the response to the initial request is a `201 Created` to indicate that +the resource has been created or `200 OK` when the resource was replaced. +The response body should be a representation of the resource that was created, +and should include a `status` field indicating the current status of the resource. +A status monitor is created to track the additional processing and the ID of the status monitor +is returned in the `Operation-Id` header of the response. +The response must also include an `Operation-Location` header for backward compatibility. +If the resource supports ETags, the response may contain an `etag` header and possibly an `etag` property in the resource. + +```text +HTTP/1.1 201 Created +Operation-Id: 22 +Operation-Location: https://items/operations/22 +etag: "123abc" + +{ + "id": "FooBar", + "status": "Provisioning", + "prop1": 555, + "prop2": "something", + "etag": "123abc" +} +``` -This diagram illustrates how a long-running operation with a status monitor is initiated and then how the client +The client will issue a GET to the status monitor to obtain the status of the operation performing the additional processing. + +```text +GET https://items/operations/22?api-version=2022-05-01 +``` + +When the additional processing completes, the status monitor indicates if it succeeded or failed. + +```text +HTTP/1.1 200 OK + +{ + "id": "22", + "status": "Succeeded" +} +``` + +If the additional processing failed, the service may delete the original resource if it is not usable in this state, +but should clearly document this behavior. + +### Long-running delete operation + +A long-running delete operation returns a `202 Accepted` with a status monitor which the client uses to determine the outcome of the delete. + +The resource being deleted should remain visible (returned from a GET) until the delete operation completes successfully. + +When the delete operation completes successfully, a client must be able to create a new resource with the same name without conflicts. + +This diagram illustrates how a long-running DELETE operation is initiated and then how the client determines it has completed and obtains its results: ```mermaid @@ -243,7 +323,7 @@ sequenceDiagram participant Client participant API Endpoint participant Status Monitor - Client->>API Endpoint: POST/DELETE + Client->>API Endpoint: DELETE API Endpoint->>Client: HTTP/1.1 202 Accepted
    { "id": "22", "status": "NotStarted" } Client->>Status Monitor: GET Status Monitor->>Client: HTTP/1.1 200 OK
    Retry-After: 5
    { "id": "22", "status": "Running" } @@ -251,8 +331,7 @@ sequenceDiagram Status Monitor->>Client: HTTP/1.1 200 OK
    { "id": "22", "status": "Succeeded" } ``` -1. The client sends the request to initiate the long-running operation. -The initial request could be a POST or DELETE method. +1. The client sends the request to initiate the long-running DELETE operation. The request may contain an `Operation-Id` header that the service uses as the ID of the status monitor created for the operation. 2. The service validates the request and initiates the operation processing. @@ -261,8 +340,8 @@ Otherwise the service responds with a `202-Accepted` HTTP status code. The response body is the status monitor for the operation including the ID, either from the request header or generated by the service. When returning a status monitor whose status is not in a terminal state, the response must also include a `retry-after` header indicating the minimum number of seconds the client should wait before polling (GETing) the status monitor URL again for an update. -For backward compatibility, the response may also include an `Operation-Location` header containing the absolute URL -of the status monitor resource (without an api-version query parameter). +For backward compatibility, the response must also include an `Operation-Location` header containing the absolute URL +of the status monitor resource, including an api-version query parameter. 3. After waiting at least the amount of time specified by the previous response's `Retry-after` header, the client issues a GET request to the status monitor using the ID in the body of the initial response. @@ -275,14 +354,11 @@ If the operation is still being processed, the status field will contain a "non- 5. After the operation processing completes, a GET request to the status monitor returns the status monitor with a status field set to a terminal value -- `Succeeded`, `Failed`, or `Canceled` -- that indicates the result of the operation. If the status is `Failed`, the status monitor resource contains an `error` field with a `code` and `message` that describes the failure. -If the status is `Succeeded` and the LRO is an Action operation, the operation results will be returned in the `result` field of the status monitor. -If the status is `Succeeded` and the LRO is an operation on a resource, the client can perform a GET on the resource -to observe the result of the operation if desired. -6. There may be some cases where a long-running operation can be completed before the response to the initial request. +6. There may be some cases where a long-running DELETE operation can be completed before the response to the initial request. In these cases, the operation should still return a `202 Accepted` with the `status` property set to the appropriate terminal state. -7. The service is responsible for purging the status-monitor resource. +7. The service is responsible for purging the status monitor resource. It should auto-purge the status monitor resource after completion (at least 24 hours). The service may offer DELETE of the status monitor resource due to GDPR/privacy. @@ -292,6 +368,9 @@ An action operation that is also long-running combines the [Action Operations](# with the [Long Running Operations](#long-running-operations) pattern. The operation is initiated with a POST operation and the operation path ends in `:`. +A long-running POST should not be used for resource create: use PUT as described above. +PATCH must never be used for long-running operations: it should be reserved for simple resource updates. +If a long-running update is required it should be implemented with POST. ```text POST /:?api-version=2022-05-01 @@ -303,7 +382,7 @@ Operation-Id: 22 } ``` -The response is a `202 Accepted` as described above. +A long-running action operation returns a `202 Accepted` response with the status monitor in the response body. ```text HTTP/1.1 202 Accepted @@ -333,74 +412,87 @@ HTTP/1.1 200 OK } ``` -### PUT with additional long-running processing +This diagram illustrates how a long-running action operation is initiated and then how the client +determines it has completed and obtains its results: -A special case of long-running operation that occurs often is a PUT operation to create or replace a resource -that involves some additional long-running processing. -One example is a resource requires physical resources (e.g. servers) to be "provisioned" to make the resource functional. -In this case, the request may contain an `Operation-Id` header that the service will use as -the ID of the status monitor created for the operation. +```mermaid +sequenceDiagram + participant Client + participant API Endpoint + participant Status Monitor + Client->>API Endpoint: POST + API Endpoint->>Client: HTTP/1.1 202 Accepted
    { "id": "22", "status": "NotStarted" } + Client->>Status Monitor: GET + Status Monitor->>Client: HTTP/1.1 200 OK
    Retry-After: 5
    { "id": "22", "status": "Running" } + Client->>Status Monitor: GET + Status Monitor->>Client: HTTP/1.1 200 OK
    { "id": "22", "status": "Succeeded", "result": { ... } } +``` -```text -PUT /items/FooBar&api-version=2022-05-01 -Operation-Id: 22 +1. The client sends the request to initiate the long-running action operation. +The request may contain an `Operation-Id` header that the service uses as the ID of the status monitor created for the operation. -{ - "prop1": 555, - "prop2": "something" -} -``` +2. The service validates the request and initiates the operation processing. +If there are any problems with the request, the service responds with a `4xx` status code and error response body. +Otherwise the service responds with a `202-Accepted` HTTP status code. +The response body is the status monitor for the operation including the ID, either from the request header or generated by the service. +When returning a status monitor whose status is not in a terminal state, the response must also include a `retry-after` header indicating the minimum number of seconds the client should wait +before polling (GETing) the status monitor URL again for an update. +For backward compatibility, the response may also include an `Operation-Location` header containing the absolute URL +of the status monitor resource, including an api-version query parameter. -In this case the response to the initial request is a `201 Created` to indicate that the resource has been created -or `200 OK` when the resource was replaced. -The response body contains a representation of the created resource, which is the standard pattern for a create operation. -A status monitor is created to track the additional processing and the ID of the status monitor -is returned in the `Operation-Id` header of the response. -The response may also include an `Operation-Location` header for backward compatibility. -If the resource supports ETags, the response may contain an `etag` header and possibly an `etag` property in the resource. +3. After waiting at least the amount of time specified by the previous response's `Retry-after` header, +the client issues a GET request to the status monitor using the ID in the body of the initial response. +The GET operation for the status monitor is documented in the REST API definition and the ID +is the last URL path segment. -```text -HTTP/1.1 201 Created -Operation-Id: 22 -Operation-Location: https://items/operations/22 -etag: "123abc" +4. The status monitor responds with information about the operation including its current status, +which should be represented as one of a fixed set of string values in a field named `status`. +If the operation is still being processed, the status field will contain a "non-terminal" value, like `NotStarted` or `Running`. -{ - "id": "FooBar", - "etag": "123abc", - "prop1": 555, - "prop2": "something" -} -``` +5. After the operation processing completes, a GET request to the status monitor returns the status monitor with a status field set to a terminal value -- `Succeeded`, `Failed`, or `Canceled` -- that indicates the result of the operation. +If the status is `Failed`, the status monitor resource contains an `error` field with a `code` and `message` that describes the failure. +If the status is `Succeeded`, the operation results (if any) are returned in the `result` field of the status monitor. -The client will issue a GET to the status monitor to obtain the status of the operation performing the additional processing. +6. There may be some cases where a long-running action operation can be completed before the response to the initial request. +In these cases, the operation should still return a `202 Accepted` with the `status` property set to the appropriate terminal state. -```text -GET https://items/operations/22?api-version=2022-05-01 -``` +7. The service is responsible for purging the status monitor resource. +It should auto-purge the status monitor resource after completion (at least 24 hours). +The service may offer DELETE of the status monitor resource due to GDPR/privacy. -When the additional processing completes, the status monitor will indicate if it succeeded or failed. +### Long-running action operation not related to a resource -```text -HTTP/1.1 200 OK +When a long-running action operation is not related to a specific resource (a batch operation is one example), +another approach is needed. -{ - "id": "22", - "status": "Succeeded" -} -``` +This type of LRO should be initiated with a PUT method on a URL that represents the operation to be performed, +and includes a final path parameter for the user-specified operation ID. +The response of the PUT includes a response body containing a representation of the status monitor for the operation +and an `Operation-Location` response header that contains the absolute URL of the status monitor. +In this type of LRO, the status monitor should include any information from the request used to initiate the operation, +so that a failed operation could be reissued if necessary. -If the additional processing failed, the service may delete the original resource if it is not usable in this state, -but would have to clearly document this behavior. +Clients will use a GET on the status monitor URL to obtain the status and results of the operation. +Since the HTTP semantic for PUT is to create a resource, the same schema should be used for the PUT request body, +the PUT response body, and the response body of the GET for the status monitor for the operation. +For this type of LRO, the status monitor URL should be the same URL as the PUT operation. -### Long-running delete operation +The following examples illustrate this pattern. -A long-running delete operation follows the general pattern of a long-running operation -- -it returns a `202 Accepted` with a status monitor which the client uses to determine the outcome of the delete. +```text +PUT /translate-operations/?api-version=2022-05-01 -The resource being deleted should remain visible (returned from a GET) until the delete operation completes successfully. + +``` + +Note that the client specifies the operation id in the URL path. -When the delete operation completes successfully, a client must be able to create new resource with same name without conflicts. +A successful response to the PUT operation should have a `201 Created` status and response body +that contains a representation of the status monitor _and_ any information from the request used to initiate the operation. + +The service is responsible for purging the status monitor after some period of time, +but no earlier than 24 hours after the completion of the operation. +The service may offer DELETE of the status monitor resource due to GDPR/privacy. ### Controlling a long-running operation @@ -408,7 +500,7 @@ It might be necessary to support some control action on a long-running operation This is implemented as a POST on the status monitor endpoint with `:` added. ```text -POST /:cancel?api-version=2022-05-01 +POST /:cancel?api-version=2022-05-01 ``` A successful response to a control operation should be a `200 OK` with a representation of the status monitor. diff --git a/azure/Guidelines.md b/azure/Guidelines.md index d981a729..b248ecea 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -1,7 +1,7 @@ # Microsoft Azure REST API Guidelines - + + +:white_check_mark: **DO** use the following pattern when implementing an LRO action operating on an existing resource: + +```text +POST /UrlToExistingResource:?api-version=& +operation-id: ` + + +``` + +The response must look like this: + +```text +202 Accepted +operation-id: +operation-location: https://operations/ + + +``` + +The request body contains information to be used to execute the action. + +For an idempotent POST (same `operation-id` and request body within some short time window), the service should return the same response as the initial request. + +For a non-idempotent POST, the service can treat the POST operation as idempotent (if performed within a short time window) or can treat the POST operation as initiating a brand new LRO action operation. + +:no_entry: **DO NOT** use a long-running POST to create a resource -- use PUT as described above. + +:white_check_mark: **DO** allow the client to pass an `Operation-Id` header with an ID for the operation's status monitor. + +:white_check_mark: **DO** generate an ID (typically a GUID) for the status monitor if the `Operation-Id` header was not passed by the client. + +:white_check_mark: **DO** fail a request with a `409-Conflict` if the `Operation-Id` header matches an existing operation unless the request is identical to the prior request (a retry scenario). + +:white_check_mark: **DO** return a `202-Accepted` status code from the request that initiates an LRO action on a resource if the processing of the operation was successfully initiated. + +:warning: **YOU SHOULD NOT** return any other `2xx` status code from the initial request of an LRO -- return `202-Accepted` and a status monitor even if processing was completed before the initiating request returns. + +:white_check_mark: **DO** return a status monitor in the response body as described in [Obtaining status and results of long-running operations](#obtaining-status-and-results-of-long-running-operations). + +#### LRO action with no related resource pattern -**OperationStatus** : Object +:white_check_mark: **DO** use the following pattern when implementing an LRO action not related to a specific resource (such as a batch operation): + +```text +PUT /?api-version= + +> +``` + +The response must look like this: + +```text +201 Created +operation-location: + + +``` + +:ballot_box_with_check: **YOU SHOULD** +define a unique operation endpoint for each LRO action with no related resource. + +:white_check_mark: **DO** require the +`Operation-Id` as the final path segment in the URL. + +Note: The `operation-id` URL segment (not header) is *required*, forcing the client to specify the status monitor's resource ID +and is also used for retries/idempotency. + +:white_check_mark: **DO** return a `201 Created` status code +with an `operation-location` response header if the LRO Action operation was accepted for processing. + +:white_check_mark: **DO** return a +status monitor in the response body that contains the operation status, request parameters, and when the operation completes either +the operation result or error. + +Note: Since all request parameters must be present in the status monitor, +the request and response body of the PUT can be defined with a single schema. + +:ballot_box_with_check: **YOU SHOULD** +return the status monitor for an operation for a subsequent GET on the URL that initiates the LRO, and use this endpoint as +the status monitor URL returned in the `operation-location` response header. + +#### The Status Monitor Resource + +All patterns that initiate a LRO either implicitly or explicitly create a [Status Monitor resource](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.3) in the service's `operations` collection. + +:white_check_mark: **DO** return a status monitor in the response body that conforms with the following structure: Property | Type | Required | Description -------- | ----------- | :------: | ----------- `id` | string | true | The unique id of the operation -`status` | string | true | enum that includes values "NotStarted", "Running", "Succeeded", "Failed", and "Canceled" -`error` | ErrorDetail | | Error object that describes the error when status is "Failed" -`result` | object | | Only for POST action-type LRO, the results of the operation when completed successfully -additional
    properties | | | Additional named or dynamic properties of the operation +`kind` | string enum | true(*) | The kind of operation +`status` | string enum | true | The operation's current status: "NotStarted", "Running", "Succeeded", "Failed", and "Canceled" +`error` | ErrorDetail | | If `status`=="Failed", contains reason for failure +`result` | object | | If `status`=="Succeeded" && Action LRO (POST or PUT), contains success result if needed +additional
    properties | | | Additional named or dynamic properties of the operation -:white_check_mark: **DO** include the `id` of the operation and any other values needed for the client to form a GET request to the status monitor (e.g. a `location` path parameter). +(*): When a status monitor endpoint supports multiple operations with different result structures or additional properties, +the status monitor **must** be polymorphic -- it **must** contain a required `kind` property that indicates the kind of long-running operation. + +#### Obtaining status and results of long-running operations -:white_check_mark: **DO** include a `Retry-After` header in the response to GET requests to the status monitor if the operation is not complete. The value of this header should be an integer number of seconds to wait before making the next request to the status monitor. +:white_check_mark: **DO** use the following pattern to allow clients to poll the current state of a Status Monitor resource: + +```text +GET /?api-version= +``` + +The response must look like this: + +```text +200 OK +retry-after: (if status not terminal) + + +``` + +:white_check_mark: **DO** support the GET method on the status monitor endpoint that returns a `200-OK` response with the current state of the status monitor. + +:ballot_box_with_check: **YOU SHOULD** allow any valid value of the `api-version` query parameter to be used in the GET operation on the status monitor. + +- Note: Clients may replace the value of `api-version` in the `operation-location` URL with a value appropriate for their application. Remember that the client initiating the LRO may not be the same client polling the LRO's status. + +:white_check_mark: **DO** include the `id` of the operation and any other values needed for the client to form a GET request to the status monitor (e.g. a `location` path parameter). :white_check_mark: **DO** include the `result` property (if any) in the status monitor for a POST action-type long-running operation when the operation completes successfully. -:no_entry: **DO NOT** include a `result` property in the status monitor for a long-running operation that is not a POST action-type long-running operation. +:no_entry: **DO NOT** include a `result` property in the status monitor for a long-running operation that is not an action-type long-running operation. + +:white_check_mark: **DO** include a `retry-after` header in the response if the operation is not complete. The value of this header should be an integer number of seconds that the client should wait before polling the status monitor again. :white_check_mark: **DO** retain the status monitor resource for some publicly documented period of time (at least 24 hours) after the operation completes. +#### Pattern to List Status Monitors + +Use the following patterns to allow clients to list Status Monitor resources. + +:ballot_box_with_check: +**YOU MAY** support a GET method on any status monitor collection URL that returns a list of the status monitors in that collection. + +:ballot_box_with_check: +**YOU SHOULD** support a list operation for any status monitor collection that includes status monitors for LRO Actions with no related resource. + +:ballot_box_with_check: +**YOU SHOULD** support the `filter` query parameter on the list operation for any polymorphic status monitor collection and support filtering on the `kind` value of the status monitor. + +For example, the following request should return all status monitor resources whose `kind` is either "VMInitializing" *or* "VMRebooting" +and whose status is "NotStarted" *or* "Succeeded". + +```text +GET /operations?filter=(kind eq 'VMInitializing' or kind eq 'VMRebooting') and (status eq 'NotStarted' or status eq 'Succeeded') +``` + ### Bring your own Storage (BYOS) + Many services need to store and retrieve data files. For this scenario, the service should not implement its own storage APIs and should instead leverage the existing Azure Storage service. When doing this, the customer "owns" the storage account and just tells your service to use it. Colloquially, we call this Bring Your Own Storage as the customer is bringing their storage account to another service. BYOS provides significant benefits to service implementors: security, performance, uptime, etc. And, of course, most Azure customers are already familiar with the Azure Storage service. @@ -958,7 +1108,7 @@ While Azure Managed Storage may be easier to get started with, as your service e :white_check_mark: **DO** use the Bring Your Own Storage pattern. -:white_check_mark: **DO** use a blob prefix for a logical folder (avoid terms such as ```directory```, ```folder```, or ```path```). +:white_check_mark: **DO** use a blob prefix for a logical folder (avoid terms such as `directory`, `folder`, or `path`). :no_entry: **DO NOT** require a fresh container per operation. @@ -1120,6 +1270,7 @@ See the [Returning String Offsets & Lengths] section in Considerations for Servi ### Distributed Tracing & Telemetry + Azure SDK client guidelines specify that client libraries must send telemetry data through the `User-Agent` header, `X-MS-UserAgent` header, and Open Telemetry. Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts. From c6331c41f4ee6d5dfb45576477f4123ae4adb5ad Mon Sep 17 00:00:00 2001 From: Alexander Batishchev Date: Wed, 8 May 2024 04:13:33 -0700 Subject: [PATCH 684/729] Update link to Azure RPC (#543) --- azure/ConsiderationsForServiceDesign.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/azure/ConsiderationsForServiceDesign.md b/azure/ConsiderationsForServiceDesign.md index 663b2e16..8dc3a480 100644 --- a/azure/ConsiderationsForServiceDesign.md +++ b/azure/ConsiderationsForServiceDesign.md @@ -36,8 +36,7 @@ _Note: Developing a new service requires the development of at least 1 (manageme A **management plane** API is implemented through the Azure Resource Manager (ARM) and is used to provision and control the operational state of resources. A **data plane** API is used by developers to implement applications. Occasionally, some operations are useful for provisioning/control and applications. In this case, the operation can appear in both APIs. -Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on [the conventions of the Azure ARM](https://github.com/cloud-and-ai-microsoft/resource-provider-contract). - +Although, best practices and patterns described in this document apply to all HTTP/REST APIs, they are especially important for **data plane** services because it is the primary interface for developers using your service. The **management plane** APIs may have other preferred practices based on the conventions of the [Azure RPC](https://aka.ms/azurerpc). ## Start with the Developer Experience A great API starts with a well thought out and designed service. Your service should define simple/understandable abstractions with each given a clear name that you use consistently throughout your API and documentation. There must also be an unambiguous relationship between these abstractions. From 076732dfa581e709f34ef259169e1afdfa081776 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 27 May 2024 08:01:57 -0700 Subject: [PATCH 685/729] Update enums.md --- graph/patterns/enums.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graph/patterns/enums.md b/graph/patterns/enums.md index a47de3ea..30374cfc 100644 --- a/graph/patterns/enums.md +++ b/graph/patterns/enums.md @@ -100,6 +100,8 @@ but it is also open for future scenarios: Additionally speaking, depending on the situation, a nullable enum can very likely be avoided by adding a `none` member. +If used, `EnumType` names should be singular if the are non-flags enums, and the names should be plural if they are flags enums. + #### Flag Enums or Collection of Enums In case an enum can have multiple values at the same time the tentation is to model the property as a collection of Enums: @@ -127,3 +129,5 @@ However, [Flagged Enums](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/ With such enum, customers can select multiple values in a single field: `displayMethod = tip | alert` + +In cases where two properties want to use the same *conceptual* `EnumType`, but one property is a collection while the other is single-values, the model should have *two* duplicate `EnumType`s, marking one as a flags enum and making its name the plural form of the non-flags enum. From f47f45279cf210a06e97cb29244fccd14e2716f3 Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Thu, 30 May 2024 08:35:50 -0700 Subject: [PATCH 686/729] Update GuidelinesGraph.md Fix grammar --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index fc488762..e11c809a 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -336,7 +336,7 @@ For a complete mapping of error codes to HTTP statuses, see ### Limitations on core types The types `user`, `group`, and `device` should not have any new structural property(s) added, without compelling justification. -Instead, model the concept represented in those property(s) as a new entity, and do one of the following: +Instead, model the concept represented in those property(s) as a new entity and do one of the following: 1. Add navigation from `user`, `group`, or `device` to the new entity. 2. Add a navigation from the new entity to `user`, `group` or `device`. From 50ab12490fbd8b50f2dc20f3be8faa00357db19b Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Thu, 30 May 2024 08:41:23 -0700 Subject: [PATCH 687/729] Update coreTypes.md Use proper noun name for graph product --- graph/articles/coreTypes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index c41bfdda..e2257e8c 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -2,11 +2,11 @@ ## Overview -Types exist in graph which are highly-connected/central to the graph ecosystem. Often, these types are in the position of containing many structural properties relevant to other APIs, because they are connected to many entities in the graph. +Types exist in Microsoft Graph which are highly-connected/central to the Microsoft Graph ecosystem. Often, these types are in the position of containing many structural properties relevant to other APIs, because they are connected to many entities in the Microsoft Graph. -Structural properties should be only added to these core types when they are properties of the entity itself and strictly not for the purpose of convenience due to the entity's position in the graph. +Structural properties should be only added to these core types when they are properties of the entity itself and strictly not for the purpose of convenience due to the entity's position in Microsoft Graph. -## Core Types in Graph +## Core Types in Microsoft Graph The following types are identified as core types, and will require strong justification to allow new structural properties to be added in all cases. From 33faa274f6c3cc3b7e92986a477ad49748125a8a Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Thu, 30 May 2024 08:44:18 -0700 Subject: [PATCH 688/729] Update coreTypes.md Rename `bankAccountInformation` to `bankAccountDetail` --- graph/articles/coreTypes.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index e2257e8c..c05c7b53 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -45,7 +45,7 @@ Do one of the following: Define the new entity type: ```xml - + @@ -54,7 +54,7 @@ Define the new entity type: Add a contained navigation from user to the new entity type: ```xml - + ``` @@ -62,7 +62,7 @@ Add a contained navigation from user to the new entity type: Define the new entity type: ```xml - + @@ -70,13 +70,13 @@ Define the new entity type: Contain the new entity type in an entity set or singleton: ```xml - + ``` Add a navigation from user to the new type: ```xml - + ``` @@ -84,7 +84,7 @@ Add a navigation from user to the new type: Define the new entity type, with a navigation to the user: ```xml - + From 5d907fcd021639bfc83c97102ad4dd62169e6011 Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Thu, 30 May 2024 08:48:07 -0700 Subject: [PATCH 689/729] Update coreTypes.md Fix grammar --- graph/articles/coreTypes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index c05c7b53..81988c13 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -2,7 +2,7 @@ ## Overview -Types exist in Microsoft Graph which are highly-connected/central to the Microsoft Graph ecosystem. Often, these types are in the position of containing many structural properties relevant to other APIs, because they are connected to many entities in the Microsoft Graph. +Types exist in Microsoft Graph which are highly-connected/central to the Microsoft Graph ecosystem. Often, these types are in the position of containing many structural properties relevant to other APIs, because they are connected to many entities in Microsoft Graph. Structural properties should be only added to these core types when they are properties of the entity itself and strictly not for the purpose of convenience due to the entity's position in Microsoft Graph. From f9d8f70d330a7681f52faa6d2a5e4f4f929ff7cb Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Thu, 30 May 2024 09:14:58 -0700 Subject: [PATCH 690/729] Update coreTypes.md Add link to nav prop article --- graph/articles/coreTypes.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index 81988c13..1a0b6982 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -17,10 +17,7 @@ The following types are identified as core types, and will require strong justif ## Alternatives to Adding Structural Properties Instead of adding a structural property to the existing type (`user`, `group` or `device`), create a new type that models the information captured in the proposed structural property(s). -Then, do one of the following: -- Add a navigation property on the existing type to the new type, containing the new type. -- Contain the new type in an entity set elsewhere, and add a navigation property to the new type on the existing type. -- Contain the new type in an entity set elsewhere, and add a navigation property to the existing type on the new type. +Then, model the relationship between the existing core type and the new type by adding a navigation property. For information on modeling with navigation properties, see [Navigation Property](../patterns/navigation-property.md). ## Example: @@ -39,7 +36,7 @@ Don't add new properties to core types such as `user`. ### Do: -Do one of the following: +Model the information by creating a new type and model the relationship to the existing core type with a navigation property. To determine which option is most appropriate, see [Navigation Property](../patterns/navigation-property.md): #### Option 1: Add a navigation property on the existing type to the new type, containing the new type. From 1982472598789d6793ca7c91f469e70cb5eb9b99 Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Thu, 30 May 2024 09:20:41 -0700 Subject: [PATCH 691/729] Update coreTypes.md Update to `existing core types` --- graph/articles/coreTypes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index 1a0b6982..038f74bb 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -16,7 +16,7 @@ The following types are identified as core types, and will require strong justif ## Alternatives to Adding Structural Properties -Instead of adding a structural property to the existing type (`user`, `group` or `device`), create a new type that models the information captured in the proposed structural property(s). +Instead of adding a structural property to the existing core type (`user`, `group` or `device`), create a new type that models the information captured in the proposed structural property(s). Then, model the relationship between the existing core type and the new type by adding a navigation property. For information on modeling with navigation properties, see [Navigation Property](../patterns/navigation-property.md). ## Example: @@ -38,7 +38,7 @@ Don't add new properties to core types such as `user`. Model the information by creating a new type and model the relationship to the existing core type with a navigation property. To determine which option is most appropriate, see [Navigation Property](../patterns/navigation-property.md): -#### Option 1: Add a navigation property on the existing type to the new type, containing the new type. +#### Option 1: Add a navigation property on the existing core type to the new type, containing the new type. Define the new entity type: ```xml @@ -55,7 +55,7 @@ Add a contained navigation from user to the new entity type: ``` -#### Option 2: Contain the new type in an entity set elsewhere, and add a navigation property to the new type on the existing type. +#### Option 2: Contain the new type in an entity set elsewhere, and add a navigation property to the new type on the existing core type. Define the new entity type: ```xml @@ -77,7 +77,7 @@ Add a navigation from user to the new type: ``` -#### Option 3: Contain the new type in an entity set elsewhere, and add a navigation property to the existing type on the new type. +#### Option 3: Contain the new type in an entity set elsewhere, and add a navigation property to the existing core type on the new type. Define the new entity type, with a navigation to the user: ```xml From 59e0cd7dd47125445952a1e962c32c110badc047 Mon Sep 17 00:00:00 2001 From: tylercleveland2 <126727176+tylercleveland2@users.noreply.github.com> Date: Thu, 30 May 2024 09:21:58 -0700 Subject: [PATCH 692/729] Update coreTypes.md Update description of core types in overview to addd "intrinsic" --- graph/articles/coreTypes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/articles/coreTypes.md b/graph/articles/coreTypes.md index 038f74bb..9484ea5c 100644 --- a/graph/articles/coreTypes.md +++ b/graph/articles/coreTypes.md @@ -2,9 +2,9 @@ ## Overview -Types exist in Microsoft Graph which are highly-connected/central to the Microsoft Graph ecosystem. Often, these types are in the position of containing many structural properties relevant to other APIs, because they are connected to many entities in Microsoft Graph. +Types exist in Microsoft Graph which are highly-connected/central to the Microsoft Graph ecosystem. Often, these types are in the position of being able to contain structural properties relevant to other APIs, because they are connected to many entities in Microsoft Graph. -Structural properties should be only added to these core types when they are properties of the entity itself and strictly not for the purpose of convenience due to the entity's position in Microsoft Graph. +Structural properties should be only added to these core types when they are intrinsic to the entity itself, and strictly not for the purpose of convenience due to the entity's position in Microsoft Graph. ## Core Types in Microsoft Graph From 170a899a13ab2b3abb12b3219b9686b8ee27dc6e Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Thu, 20 Jun 2024 12:12:52 -0700 Subject: [PATCH 693/729] fix namespace guidance: -require alias, as per [ADR-030](https://microsoft.sharepoint.com/teams/APICouncil/_layouts/OneNote.aspx?id=%2Fteams%2FAPICouncil%2FSiteAssets%2FAPI%20Council%20Notebook&wd=target%28ADRs.one%7CCC35B798-B670-4554-9FCF-1682729EFA23%2FADR-030%20Namespace%20Aliases%7C363534F8-EFBD-4201-9F11-DB02AD5B7FAA%2F%29) -support multiple levels below microsoft.graph (as per [ADR-030]) -clarify uniqueness across namespaces -clarify that any change to a custom namespace name is breaking -add alias to the examples as per above --- graph/patterns/namespace.md | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/graph/patterns/namespace.md b/graph/patterns/namespace.md index 46d282cc..e4b6fef8 100644 --- a/graph/patterns/namespace.md +++ b/graph/patterns/namespace.md @@ -16,31 +16,39 @@ API designers can use the namespace attribute of the CSDL schema to declare a namespace and logically organize related API entities in the Microsoft Graph metadata. ```XML - + ... ``` A public namespace must contain the `microsoft.graph.` prefix and be presented in camel -case; that is, `microsoft.graph.myNamespace`. +case; that is, `microsoft.graph.myNamespace`. Elements defined in namespaces not prefixed +with `microsoft.graph` will be mapped to the public `microsoft.graph` namespace. + +Namespaces should not include more than two segments following the `microsoft.graph` prefix; +that is, `microsoft.graph.myNamespace.mySubNamespace`. + +Public namespaces must define an alias, and that alias must be the concatenation of +the segments following the `microsoft.graph` prefix with proper camel casing rules applied; +that is, `myNamespaceMySubNamespace`. When type casting is required in the API query, request, or response, a fully -qualified type name is represented as concatenation of a namespace and a type -name. For a consistent user experience, namespaces MUST be aligned with the corresponding API category path segment. +qualified type name is represented as concatenation of the namespace or alias, +followed by a dot (`.`) and the type name. ## When to use this pattern API resource grouping creates a user-friendly experience, keeping all resources for a specific feature close together and limiting the length of IDE prompts such as auto-complete in some programming languages. -We recommend that a new namespace be aligned with a top-level API category. +For a consistent user experience, new namespace should be aligned with a top-level API category. ## Issues and considerations -- Microsoft Graph consistency requirements discourage using the same type names for different concepts even within different namespaces. Microsoft Graph type names must be descriptive and unique within the API surface without requiring full qualification. +- Microsoft Graph consistency requirements discourage using the same type names for different concepts even within different namespaces. Microsoft Graph type names must be descriptive and should represent a single concept across the API Surface. - A namespace must be consistent with an API category in the navigation path according to [Microsoft Graph REST API Guidelines](../GuidelinesGraph.md#uniform-resource-locators-urls). -- When type name is ambiguous and requires a namespace qualifier, changing a namespace is a breaking change. +- Changing a namespace prefixed with `microsoft.graph`, or moving types between, into, or out of a namespace prefixed with `microsoft.graph`, is a breaking change. - To extend a type in a different schema, a service must declare that schema and the type in it. This is conceptually similar to .NET partial types. @@ -51,17 +59,19 @@ We recommend that a new namespace be aligned with a top-level API category. - Microsoft Graph has some predefined constraints for declared namespaces: - All public namespaces must have the prefix `microsoft.graph`. + + - Public namespaces must declare an alias that is the concatenation of the segments following the `microsoft.graph` prefix. + + - At most, two levels of nesting below `microsoft.graph` is recommended. - - Only one level of nesting deeper than `microsoft.graph` is supported. - - - If a namespace does not begin with the `microsoft.graph` prefix, all types in the schema are coerced into the main `microsoft.graph` namespace. + - If a namespace does not begin with the `microsoft.graph` prefix, all types in the schema are mapped into the public `microsoft.graph` namespace. ## Examples ### Namespace and type declarations ```XML -”\> +”\> … @@ -88,7 +98,7 @@ declarations) to match this example. - + @@ -111,4 +121,4 @@ declarations) to match this example. -``` +``` \ No newline at end of file From 400d7ca8d0e631dcb911dae0516cf07b70dacbe6 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:26:49 -0400 Subject: [PATCH 694/729] Update graph/patterns/enums.md Co-authored-by: Dan Kershaw [MSFT] --- graph/patterns/enums.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/patterns/enums.md b/graph/patterns/enums.md index 30374cfc..926864e0 100644 --- a/graph/patterns/enums.md +++ b/graph/patterns/enums.md @@ -130,4 +130,4 @@ With such enum, customers can select multiple values in a single field: `displayMethod = tip | alert` -In cases where two properties want to use the same *conceptual* `EnumType`, but one property is a collection while the other is single-values, the model should have *two* duplicate `EnumType`s, marking one as a flags enum and making its name the plural form of the non-flags enum. +In cases where two properties want to use the same *conceptual* `EnumType`, but one property is a collection while the other is single-values, the model should define *two* separate `EnumType`s, one being a non-flags enum with a singular name and the other marked as a flags enum with its name being the plural form of the non-flags enum. From 8841d61310e3c9c4b41e845531121f15cd33b690 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:17:58 -0700 Subject: [PATCH 695/729] Update collections.md --- graph/articles/collections.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 6a24d9f0..1172d8c0 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -316,3 +316,7 @@ Content-Type: application/json "value": [] } ``` + +## 11. Collections of structural types (complex types or primitive types) + +Collections of entity types are generally preferable to collections of structual types because collections of structural types must be updated as a single unit, meaning that they are overwritten entirely by new contents, rather than be updated relative to the existing contents. From e7f42d8b31d48625696c05b9224f614b88e58a18 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:33:42 -0700 Subject: [PATCH 696/729] Update collections.md --- graph/articles/collections.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 1172d8c0..aec7e1b0 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -320,3 +320,9 @@ Content-Type: application/json ## 11. Collections of structural types (complex types or primitive types) Collections of entity types are generally preferable to collections of structual types because collections of structural types must be updated as a single unit, meaning that they are overwritten entirely by new contents, rather than be updated relative to the existing contents. + +Sometimes, structural collection properties are added to a type and then scenarios are discovered later that require a collection of entity types. In these cases, there are two options forward: + +### 11.1 TODO side-by-side + +### 11.2 TODO $select trickery From 3781028e0963e3b910a24778eb4ff2338bee049a Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:38:07 -0700 Subject: [PATCH 697/729] Update collections.md --- graph/articles/collections.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index aec7e1b0..4783797c 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -325,4 +325,38 @@ Sometimes, structural collection properties are added to a type and then scenari ### 11.1 TODO side-by-side +If there's an entity type `foo` that has a collection of `bar`s: + +```xml + + + + + + + + +``` +and a scenario arises that requires, for example, to remove individual `bar`s from the collection, the model can be updated to have two collections side-by-side: + +```diff + + ++ + + + + + + + ++ ++ ++ ++ ++ ++ ++ +``` + ### 11.2 TODO $select trickery From 6c2789b678270ed33b6aa2e2229c8f4c83ebaa42 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:40:17 -0700 Subject: [PATCH 698/729] Update collections.md --- graph/articles/collections.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 4783797c..052b5019 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -329,6 +329,10 @@ If there's an entity type `foo` that has a collection of `bar`s: ```xml + + + + @@ -338,9 +342,12 @@ If there's an entity type `foo` that has a collection of `bar`s: ``` and a scenario arises that requires, for example, to remove individual `bar`s from the collection, the model can be updated to have two collections side-by-side: - ```diff + + + + + @@ -358,5 +365,11 @@ and a scenario arises that requires, for example, to remove individual `bar`s fr + + ``` +Clients will now be able to refer to individual `bar`s using `prop1` as a key, and they can now remove those `bar`s using `DELETE` requests: +```http +DELETE /foos/{fooId}/bars/{some_prop1} +``` +```json +``` ### 11.2 TODO $select trickery From e7edd544c8ef214fcb1fd6e258f01f378e893fb0 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:55:00 -0700 Subject: [PATCH 699/729] Update collections.md --- graph/articles/collections.md | 90 ++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 052b5019..45ef5a2d 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -369,7 +369,95 @@ Clients will now be able to refer to individual `bar`s using `prop1` as a key, a ```http DELETE /foos/{fooId}/bars/{some_prop1} ``` -```json +```http +HTTP/1.1 204 No Content +``` +The expectation is that `bars` and `barsAsEntities` are treated as two "views" into the same data. +To meet this expectation, workloads must: +1. Keep the properties consistent between `bar` and `barAsEntity`. +Any changes to one type must be reflected in the other type. +2. Reject requests that update both collections at the same time. +A request that adds an item to `barsAsEntities` while replacing the content of `bars` must rejected with a `400`, for example: +```http +PATCH /foos/{fooId} +{ + "bars": [ + { + "prop1": "some value", + "prop2": "another value" + } + ], + "barsAsEntities@delta": [ + { + "prop1": "a key value", + "prop2": "some new value" + } + ] +} +``` +```http +HTTP/1.1 400 Bad Request +{ + "error": { + "code": "badRequest", + "message": "'bars' and 'barsAsEntities' cannot be updated in the same request.", +} ``` +TODO should this be a 409 conflict instead? +TODO implement this in WebApi + ### 11.2 TODO $select trickery + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9b32887c882c5a3e6ff2573f8ccbccfc26fd72e3 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:55:24 -0700 Subject: [PATCH 700/729] Update collections.md --- graph/articles/collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 45ef5a2d..cd00c070 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -323,7 +323,7 @@ Collections of entity types are generally preferable to collections of structual Sometimes, structural collection properties are added to a type and then scenarios are discovered later that require a collection of entity types. In these cases, there are two options forward: -### 11.1 TODO side-by-side +### 11.1 Side-by-side collection properties If there's an entity type `foo` that has a collection of `bar`s: From dd9fd4e448f05b1045c34cf23367d9c41cf8bb94 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:58:21 -0700 Subject: [PATCH 701/729] Update collections.md --- graph/articles/collections.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index cd00c070..80ee2654 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -320,12 +320,8 @@ Content-Type: application/json ## 11. Collections of structural types (complex types or primitive types) Collections of entity types are generally preferable to collections of structual types because collections of structural types must be updated as a single unit, meaning that they are overwritten entirely by new contents, rather than be updated relative to the existing contents. - -Sometimes, structural collection properties are added to a type and then scenarios are discovered later that require a collection of entity types. In these cases, there are two options forward: - -### 11.1 Side-by-side collection properties - -If there's an entity type `foo` that has a collection of `bar`s: +Sometimes, structural collection properties are added to a type and then scenarios are discovered later that require a collection of entity types. +Take the following model with an entity type `foo` that has a collection of `bar`s: ```xml @@ -341,7 +337,12 @@ If there's an entity type `foo` that has a collection of `bar`s: ``` -and a scenario arises that requires, for example, to remove individual `bar`s from the collection, the model can be updated to have two collections side-by-side: +and a scenario arises that requires, for example, to remove individual `bar`s from the collection. +There are two options forward: + +### 11.1 Side-by-side collection properties + +The model can be updated to have two collections side-by-side: ```diff @@ -407,7 +408,7 @@ HTTP/1.1 400 Bad Request TODO should this be a 409 conflict instead? TODO implement this in WebApi -### 11.2 TODO $select trickery +### 11.2 `$select` overloading From 2a0091fde9efae5453cec75b7f7a5955ff5e7dcf Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:01:32 -0700 Subject: [PATCH 702/729] Update collections.md --- graph/articles/collections.md | 65 ++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 80ee2654..f7cc235a 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -350,7 +350,7 @@ The model can be updated to have two collections side-by-side: -+ ++ @@ -410,6 +410,69 @@ TODO implement this in WebApi ### 11.2 `$select` overloading +The model can be updated to simply switch the complex type for an entity type: +```diff + + + + + +- ++ + + +- ++ ++ ++ ++ + + +- ++ +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7823796fe4f337ec65a0c8d5eb92d062b3c21a9a Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:09:00 -0700 Subject: [PATCH 703/729] Update collections.md --- graph/articles/collections.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index f7cc235a..8948e338 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -431,8 +431,31 @@ The model can be updated to simply switch the complex type for an entity type: - + ``` +To maintain backwards compatibility **and** compliance with the OData standard, there are several semantic changes that the workload must address: +1. Existing clients would have been able to `$select` the `bars` property. +Now that `bars` is a navigation property, the [OData standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361040) specifies that its navigation link be returned when it is `$selected`: +> If the select item is a navigation property, then the corresponding navigation link is represented in the response. +Because the previous behavior for `$select=bars` was to include the collection in the response, and because the standard dictates that the navigation link be included in the response, the new behavior is to include both: + +```http +GET /foos/{fooId}?$select=bars +``` +```http +200 OK +{ + "id": "{fooId}", + "bars": [ + { + "prop1": "some value", + "prop2": "another value" + }, + ... + ] + "bars@odata.navigationLink": "/foos('{fooId}')/bars" +} +``` From a59f82090e4608ec02209eefab849153f9582a5f Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:13:26 -0700 Subject: [PATCH 704/729] Update collections.md --- graph/articles/collections.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 8948e338..2c95438c 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -457,6 +457,8 @@ GET /foos/{fooId}?$select=bars } ``` +2. The default behavior for structural collections is to include them in the response payload for their containing entity. If this was the behavior of `foo` before, it must be preserved by **auto-expanding** the `bars` property now that it is a navigation property (because the default behavior for navigation properties is to **not** expand them). +3. Structural collections are updated using `PATCH` requests to replace the entire contents of the collection. The new navigation property must preserve this behavior. From 913775606b41e430cd8d140307ce34815496bbbb Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:14:06 -0700 Subject: [PATCH 705/729] Update collections.md --- graph/articles/collections.md | 93 +---------------------------------- 1 file changed, 2 insertions(+), 91 deletions(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 2c95438c..167e90b1 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -338,7 +338,7 @@ Take the following model with an entity type `foo` that has a collection of `bar ``` and a scenario arises that requires, for example, to remove individual `bar`s from the collection. -There are two options forward: +There are two options forward: //// TODO do we want to offer both options, or just one? ### 11.1 Side-by-side collection properties @@ -460,93 +460,4 @@ GET /foos/{fooId}?$select=bars 2. The default behavior for structural collections is to include them in the response payload for their containing entity. If this was the behavior of `foo` before, it must be preserved by **auto-expanding** the `bars` property now that it is a navigation property (because the default behavior for navigation properties is to **not** expand them). 3. Structural collections are updated using `PATCH` requests to replace the entire contents of the collection. The new navigation property must preserve this behavior. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TODO implement this in webapi From 955e9ec0c94dc9f7dbe003c22d6ee9a992cbb5b3 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:40:45 -0700 Subject: [PATCH 706/729] Update GuidelinesGraph.md --- graph/GuidelinesGraph.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index e11c809a..bdc282dc 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -99,7 +99,9 @@ Following is a short summary of the most often used conventions. | :no_entry: **MUST NOT** use redundant words in names. | - **Right:** /places/{id}/**displayName** or /phones/{id}/**number**
    - **Wrong:** /places/{id}/**placeName** or /phones/{id}/**phoneNumber** | | :warning: **SHOULD NOT** use brand names in type or property names. | - **Right:** chat
    - **Wrong:** teamsChat
    - **NOTE:** there is an exception for resources that *only* exist under the `/admin` root segment and the `/users/{userId}/settings` path. | | :warning: **SHOULD NOT** use acronyms or abbreviations unless they are broadly understood. | - **Right:** url or htmlSignature
    - **Wrong:** msodsUrl or dlp | -| :heavy_check_mark: **MUST** use singular nouns for type names. | - **Right:** address
    - **Wrong:** addresses | +| :heavy_check_mark: **MUST** use singular nouns for non-enum type names. | - **Right:** address
    - **Wrong:** addresses | +| :heavy_check_mark: **MUST** use singular nouns for non-flags enum type names. | - **Right:** color
    - **Wrong:** colors | +| :heavy_check_mark: **MUST** use plural nouns for flags enum type names. | - **Right:** diplayMethods
    - **Wrong:** displayMethod | | :heavy_check_mark: **MUST** use plural nouns for collections (for listing type or collection properties). | - **Right:** addresses
    - **Wrong:** address | | :ballot_box_with_check: **SHOULD** pluralize the noun even when followed by an adjective (a *postpositive*).| - **Right:** passersby or mothersInLaw
    - **Wrong:** notaryPublics or motherInLaws | | **CASING** | | From b7fb27518c9a8a0779caf7129cc821a91b17082e Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:44:49 -0700 Subject: [PATCH 707/729] Update enums.md --- graph/patterns/enums.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/graph/patterns/enums.md b/graph/patterns/enums.md index 926864e0..080435af 100644 --- a/graph/patterns/enums.md +++ b/graph/patterns/enums.md @@ -131,3 +131,13 @@ With such enum, customers can select multiple values in a single field: `displayMethod = tip | alert` In cases where two properties want to use the same *conceptual* `EnumType`, but one property is a collection while the other is single-values, the model should define *two* separate `EnumType`s, one being a non-flags enum with a singular name and the other marked as a flags enum with its name being the plural form of the non-flags enum. + +#### Flag enum + non-flag enum + +There are occasions where one API will want to use a non-flag enum, but another API will want a flags enum. +For example, the `displayMethod` example above may have one API that is configuring which display methods to use, and another API which is configuring that particular display method. +In this case, the first API will want a flags enum, but the second API will want to only allow configuring one display method at a time, and will therefore prefer a non-flags enum. + +Two enum types should be defined, one as a flags enum and the other as a non-flags enum. +The flags enum should be named such that it is plural, and the non-flags enum should be named such that it is singular. +The two types should be kept in sync with each other. From 9c93e8c18612c10187be4ab1b0b6674f597b486e Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:45:57 -0700 Subject: [PATCH 708/729] Update enums.md --- graph/patterns/enums.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/graph/patterns/enums.md b/graph/patterns/enums.md index 080435af..926864e0 100644 --- a/graph/patterns/enums.md +++ b/graph/patterns/enums.md @@ -131,13 +131,3 @@ With such enum, customers can select multiple values in a single field: `displayMethod = tip | alert` In cases where two properties want to use the same *conceptual* `EnumType`, but one property is a collection while the other is single-values, the model should define *two* separate `EnumType`s, one being a non-flags enum with a singular name and the other marked as a flags enum with its name being the plural form of the non-flags enum. - -#### Flag enum + non-flag enum - -There are occasions where one API will want to use a non-flag enum, but another API will want a flags enum. -For example, the `displayMethod` example above may have one API that is configuring which display methods to use, and another API which is configuring that particular display method. -In this case, the first API will want a flags enum, but the second API will want to only allow configuring one display method at a time, and will therefore prefer a non-flags enum. - -Two enum types should be defined, one as a flags enum and the other as a non-flags enum. -The flags enum should be named such that it is plural, and the non-flags enum should be named such that it is singular. -The two types should be kept in sync with each other. From 978d8473fa6f8f08662a89331d854e280204675a Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:46:20 -0700 Subject: [PATCH 709/729] Update enums.md --- graph/patterns/enums.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/graph/patterns/enums.md b/graph/patterns/enums.md index 926864e0..080435af 100644 --- a/graph/patterns/enums.md +++ b/graph/patterns/enums.md @@ -131,3 +131,13 @@ With such enum, customers can select multiple values in a single field: `displayMethod = tip | alert` In cases where two properties want to use the same *conceptual* `EnumType`, but one property is a collection while the other is single-values, the model should define *two* separate `EnumType`s, one being a non-flags enum with a singular name and the other marked as a flags enum with its name being the plural form of the non-flags enum. + +#### Flag enum + non-flag enum + +There are occasions where one API will want to use a non-flag enum, but another API will want a flags enum. +For example, the `displayMethod` example above may have one API that is configuring which display methods to use, and another API which is configuring that particular display method. +In this case, the first API will want a flags enum, but the second API will want to only allow configuring one display method at a time, and will therefore prefer a non-flags enum. + +Two enum types should be defined, one as a flags enum and the other as a non-flags enum. +The flags enum should be named such that it is plural, and the non-flags enum should be named such that it is singular. +The two types should be kept in sync with each other. From 00c5e08fa30fc19c08f844e3e775b6e6a012a46f Mon Sep 17 00:00:00 2001 From: apiary Date: Thu, 15 Aug 2024 16:47:33 -0700 Subject: [PATCH 710/729] Fixed wording Fixed wording from `modeling behavior` to `behavior modeling` --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index bdc282dc..4aac6a65 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -263,7 +263,7 @@ The HTTP operations dictate how your API behaves. The URL of an API, along with If possible, APIs SHOULD use resource-based designs with standard HTTP methods rather than operation resources. Operation resources are either functions or actions. According to [OData standards](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#_Toc31359009), a function represents an operation that returns a single instance or collection of instances of any type and doesn’t have an observable side effect. An action might have side effects and might return a result represented as a single entity or collection of any type. -| Microsoft Graph rules for modeling behavior | +| Microsoft Graph rules for behavior modeling | |------------------------------------------------------------------| | :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.
    This approach requires the service to produce a system-generated key, or for a caller to provide a key in the request payload. | | :ballot_box_with_check: **SHOULD** additionally use PATCH to create new entities in insertable entity sets or collections.
    This [Upsert](./patterns/upsert.md) approach requires the caller to provide a key in the request URL. | From b4f24bc5a30b9c09f35efb5e9f83ca6a8a069357 Mon Sep 17 00:00:00 2001 From: apiary Date: Thu, 15 Aug 2024 17:45:55 -0700 Subject: [PATCH 711/729] Corrected RFC reference Corrected RFC reference --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index bdc282dc..d1012038 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -275,7 +275,7 @@ If possible, APIs SHOULD use resource-based designs with standard HTTP methods r Operation resources MUST have a binding parameter that matches the type of the bound resource. In addition, both actions and functions support overloading, meaning an API definition might contain multiple actions or functions with the same name. -For an additional list of standard HTTP methods, see the [RFC7230](https://www.rfc-editor.org/rfc/rfc9112)). +For an additional list of standard HTTP methods, see the [RFC7231 Hypertext Transfer Protocol](https://www.rfc-editor.org/rfc/rfc7231). ### Error handling From aded31f414582b89a86778944c8c631eaa0f6666 Mon Sep 17 00:00:00 2001 From: dkershaw Date: Thu, 12 Sep 2024 16:05:15 +0100 Subject: [PATCH 712/729] Updated naming rule for Booleans --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 4aac6a65..ea684fbf 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -116,7 +116,7 @@ Following is a short summary of the most often used conventions. | :ballot_box_with_check: **SHOULD** use the Duration type for durations, but if using an `int`, append the units. | - **Right:** passwordValidityPeriod—an Edm.Duration
    - **Right:** passwordValidityPeriodInDays — an Edm.Int32 (use of Edm.Duration type is preferable)
    - **Wrong:** passwordValidityPeriod — an Edm.Int32 | | :no_entry: **MUST NOT** use suffix property names with primitive type names unless the type is temporal. | - **Right:** isEnabled or amount
    - **Wrong:** enabledBool | | :ballot_box_with_check: **SHOULD** prefix property names for properties concerning a different entity. | - **Right:** siteWebUrl on driveItem or userId on auditActor
    - **Wrong:** webUrl on contact when it's the companyWebUrl | -| :ballot_box_with_check: **SHOULD** prefix Boolean properties with `is`, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong:** enabled or allowResourceAccount
    - **Right:** allowNewTimeProposals or allowInvitesFrom (subjectively more natural than the following examples)
    - **Wrong:** isNewTimeProposalsAllowed or isInvitesFromAllowed (subjectively more awkward that the preceding examples) | +| :ballot_box_with_check: **SHOULD** prefix Boolean properties with `is`, unless this leads to awkward or unnatural sounding names for Boolean properties. | - **Right:** isEnabled or isResourceAccount
    - **Wrong:** enabled or allowResourceAccount
    - **Right:** hasChildren or hasSubscriptions
    - **Wrong:** isChildren or isSubscriptions
    - **Right:** allowNewTimeProposals or allowInvitesFrom (subjectively more natural than the following examples)
    - **Wrong:** isNewTimeProposalsAllowed or isInvitesFromAllowed (subjectively more awkward that the preceding examples) | | :no_entry: **MUST NOT** use collection, response, or request suffixes. | - **Right:** addresses
    - **Wrong:** addressCollection | #### Tree and graph structures From d878b734cc8ab5fbe353f2a1c6db5bf52d57b7d0 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:39:53 -0800 Subject: [PATCH 713/729] Update collections.md --- graph/articles/collections.md | 99 ++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 167e90b1..703819c7 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -321,77 +321,79 @@ Content-Type: application/json Collections of entity types are generally preferable to collections of structual types because collections of structural types must be updated as a single unit, meaning that they are overwritten entirely by new contents, rather than be updated relative to the existing contents. Sometimes, structural collection properties are added to a type and then scenarios are discovered later that require a collection of entity types. -Take the following model with an entity type `foo` that has a collection of `bar`s: +Take the following model with an entity type `application` that has a collection of `keyCredential`s: ```xml - + - + + ... - - - + + + + ... ``` -and a scenario arises that requires, for example, to remove individual `bar`s from the collection. +and a scenario arises that requires, for example, to remove individual `keyCredential`s from the collection. There are two options forward: //// TODO do we want to offer both options, or just one? ### 11.1 Side-by-side collection properties The model can be updated to have two collections side-by-side: ```diff - + - -+ + ++ - - - + + + -+ ++ + -+ ++ + -+ -+ ++ ++ + ``` -Clients will now be able to refer to individual `bar`s using `prop1` as a key, and they can now remove those `bar`s using `DELETE` requests: +Clients will now be able to refer to individual `keyCredential`s using `keyId` as a key, and they can now remove those `keyCredential`s using `DELETE` requests: ```http -DELETE /foos/{fooId}/bars/{some_prop1} +DELETE /applications/{applicationId}/keyCredentials/{some_keyId} ``` ```http HTTP/1.1 204 No Content ``` -The expectation is that `bars` and `barsAsEntities` are treated as two "views" into the same data. +The expectation is that `keyCredentials` and `keyCredentialsAsEntities` are treated as two "views" into the same data. To meet this expectation, workloads must: -1. Keep the properties consistent between `bar` and `barAsEntity`. +1. Keep the properties consistent between `keyCredential` and `keyCredentialAsEntity`. Any changes to one type must be reflected in the other type. 2. Reject requests that update both collections at the same time. -A request that adds an item to `barsAsEntities` while replacing the content of `bars` must rejected with a `400`, for example: +A request that adds an item to `keyCredentialsAsEntities` while replacing the content of `keyCredentials` must rejected with a `400`, for example: ```http -PATCH /foos/{fooId} +PATCH /applications/{applicationId} { - "bars": [ + "keyCredentials": [ { - "prop1": "some value", - "prop2": "another value" + "keyId": "10000000-0000-0000-0000-000000000000", + "endDateTime": "2012-12-03T07:16:23Z" } ], - "barsAsEntities@delta": [ + "keyCredentialsAsEntities@delta": [ { - "prop1": "a key value", - "prop2": "some new value" + "keyId": "20000000-0000-0000-0000-000000000000", + "endDateTime": "2012-12-03T07:16:23Z" } ] } @@ -401,7 +403,7 @@ HTTP/1.1 400 Bad Request { "error": { "code": "badRequest", - "message": "'bars' and 'barsAsEntities' cannot be updated in the same request.", + "message": "'keyCredentials' and 'keyCredentialsAsEntities' cannot be updated in the same request.", } ``` @@ -412,52 +414,53 @@ TODO implement this in WebApi The model can be updated to simply switch the complex type for an entity type: ```diff - + -- -+ +- ++ -- -+ +- ++ + -+ ++ + - - + + - + ``` To maintain backwards compatibility **and** compliance with the OData standard, there are several semantic changes that the workload must address: -1. Existing clients would have been able to `$select` the `bars` property. -Now that `bars` is a navigation property, the [OData standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361040) specifies that its navigation link be returned when it is `$selected`: +1. Existing clients would have been able to `$select` the `keyCredentials` property. +Now that `keyCredentials` is a navigation property, the [OData standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361040) specifies that its navigation link be returned when it is `$selected`: > If the select item is a navigation property, then the corresponding navigation link is represented in the response. -Because the previous behavior for `$select=bars` was to include the collection in the response, and because the standard dictates that the navigation link be included in the response, the new behavior is to include both: +Because the previous behavior for `$select=keyCredentials` was to include the collection in the response, and because the standard dictates that the navigation link be included in the response, the new behavior is to include both: ```http -GET /foos/{fooId}?$select=bars +GET /applications/{applicationId}?$select=keyCredentials ``` ```http 200 OK { - "id": "{fooId}", - "bars": [ + "id": "{applicationId}", + "keyCredentials": [ { - "prop1": "some value", - "prop2": "another value" + "keyId": "30000000-0000-0000-0000-000000000000", + "endDateTime": "2012-12-03T07:16:23Z", + ... }, ... ] - "bars@odata.navigationLink": "/foos('{fooId}')/bars" + "keyCredentials@odata.navigationLink": "/applications('{applicationId}')/keyCredentials" } ``` -2. The default behavior for structural collections is to include them in the response payload for their containing entity. If this was the behavior of `foo` before, it must be preserved by **auto-expanding** the `bars` property now that it is a navigation property (because the default behavior for navigation properties is to **not** expand them). +2. The default behavior for structural collections is to include them in the response payload for their containing entity. If this was the behavior of `application` before, it must be preserved by **auto-expanding** the `keyCredentials` property now that it is a navigation property (because the default behavior for navigation properties is to **not** expand them). 3. Structural collections are updated using `PATCH` requests to replace the entire contents of the collection. The new navigation property must preserve this behavior. TODO implement this in webapi From 0e22ae89319504e26592b8bf1301e90d3a69b2fb Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:41:37 -0800 Subject: [PATCH 714/729] Update collections.md --- graph/articles/collections.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 703819c7..aba35be9 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -48,7 +48,7 @@ GET https://graph.microsoft.com/beta/teamwork/devices/0f3ce432-e432-0f3c-32e4-3c "value": { "@odata.type": "#microsoft.graph.teamworkDevice", "id": "0f3ce432-e432-0f3c-32e4-3c0f32e43c0f", - "deviceType": "CollaborationBar", + "deviceType": "CollaborationBar",371 "hardwareDetail": { "serialNumber": "0189", "uniqueId": "5abcdefgh", @@ -370,7 +370,7 @@ The model can be updated to have two collections side-by-side: ``` Clients will now be able to refer to individual `keyCredential`s using `keyId` as a key, and they can now remove those `keyCredential`s using `DELETE` requests: ```http -DELETE /applications/{applicationId}/keyCredentials/{some_keyId} +DELETE /applications/{applicationId}/keyCredentialsAsEntities/{some_keyId} ``` ```http HTTP/1.1 204 No Content From 303804fb2411b241e4dd567ee1dc0a35fd8962a7 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:42:33 -0800 Subject: [PATCH 715/729] Update graph/articles/collections.md --- graph/articles/collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index aba35be9..a42b0abf 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -455,7 +455,7 @@ GET /applications/{applicationId}?$select=keyCredentials ... }, ... - ] + ], "keyCredentials@odata.navigationLink": "/applications('{applicationId}')/keyCredentials" } ``` From fa9198a505ca6960c4c9162055ba7e1ad48a79c8 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:54:29 -0800 Subject: [PATCH 716/729] Update collections.md --- graph/articles/collections.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index a42b0abf..82458b94 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -351,7 +351,19 @@ The model can be updated to have two collections side-by-side: - + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + From 560756b149dfebc758ef83b982d24e6caa2ddb8c Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:55:22 -0800 Subject: [PATCH 717/729] Update collections.md --- graph/articles/collections.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 82458b94..deabdcda 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -344,7 +344,7 @@ There are two options forward: //// TODO do we want to offer both options, or ju ### 11.1 Side-by-side collection properties -The model can be updated to have two collections side-by-side: +The model can be updated to have two collections side-by-side, deprecating the existing one: ```diff @@ -387,7 +387,7 @@ DELETE /applications/{applicationId}/keyCredentialsAsEntities/{some_keyId} ```http HTTP/1.1 204 No Content ``` -The expectation is that `keyCredentials` and `keyCredentialsAsEntities` are treated as two "views" into the same data. +While both properties exist on graph, the expectation is that `keyCredentials` and `keyCredentialsAsEntities` are treated as two "views" into the same data. To meet this expectation, workloads must: 1. Keep the properties consistent between `keyCredential` and `keyCredentialAsEntity`. Any changes to one type must be reflected in the other type. From bb24f133213326bffcd6d03e7747b58e51937a3b Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:55:48 -0800 Subject: [PATCH 718/729] Update collections.md --- graph/articles/collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index deabdcda..78e91656 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -48,7 +48,7 @@ GET https://graph.microsoft.com/beta/teamwork/devices/0f3ce432-e432-0f3c-32e4-3c "value": { "@odata.type": "#microsoft.graph.teamworkDevice", "id": "0f3ce432-e432-0f3c-32e4-3c0f32e43c0f", - "deviceType": "CollaborationBar",371 + "deviceType": "CollaborationBar", "hardwareDetail": { "serialNumber": "0189", "uniqueId": "5abcdefgh", From 95063f934f3b83e46e3b123fff0c3fba48c7d474 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:57:51 -0800 Subject: [PATCH 719/729] Update collections.md --- graph/articles/collections.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 78e91656..6a87af97 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -342,7 +342,7 @@ Take the following model with an entity type `application` that has a collection and a scenario arises that requires, for example, to remove individual `keyCredential`s from the collection. There are two options forward: //// TODO do we want to offer both options, or just one? -### 11.1 Side-by-side collection properties +### 11.1 Side-by-side collection properties (for any collection of structural types) The model can be updated to have two collections side-by-side, deprecating the existing one: ```diff @@ -422,7 +422,7 @@ HTTP/1.1 400 Bad Request TODO should this be a 409 conflict instead? TODO implement this in WebApi -### 11.2 `$select` overloading +### 11.2 Redefine as Entity Type (for collections of complex types) The model can be updated to simply switch the complex type for an entity type: ```diff From cdcb0e383c0c62fdad441acd6fffdf58a18fbaef Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:58:43 -0800 Subject: [PATCH 720/729] Update graph/articles/collections.md Co-authored-by: Michael Pizzo --- graph/articles/collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 6a87af97..cfd41b4b 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -473,6 +473,6 @@ GET /applications/{applicationId}?$select=keyCredentials ``` 2. The default behavior for structural collections is to include them in the response payload for their containing entity. If this was the behavior of `application` before, it must be preserved by **auto-expanding** the `keyCredentials` property now that it is a navigation property (because the default behavior for navigation properties is to **not** expand them). -3. Structural collections are updated using `PATCH` requests to replace the entire contents of the collection. The new navigation property must preserve this behavior. +3. Structural collections can be updated using a `PATCH` request to the containing entity to replace the entire contents of the collection. If the service supported such updates to the structural collection, then updates to the new navigation property must preserve this behavior. TODO implement this in webapi From 4d8039d5ad9a44c0d28a05850b2711f4786ccdd2 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:58:57 -0800 Subject: [PATCH 721/729] Update graph/articles/collections.md Co-authored-by: Michael Pizzo --- graph/articles/collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index cfd41b4b..6fba8566 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -319,7 +319,7 @@ Content-Type: application/json ## 11. Collections of structural types (complex types or primitive types) -Collections of entity types are generally preferable to collections of structual types because collections of structural types must be updated as a single unit, meaning that they are overwritten entirely by new contents, rather than be updated relative to the existing contents. +Entity types are generally preferred for collections since complex types within a collection cannot be individually referenced. Collections of complex types, including any nested properties, must be updated as a single unit, entirely replacing the existing contents. Even if your API is read-only today, modeling it as a collection of entities gives you more flexibility in referencing individual members now and in the future. Sometimes, structural collection properties are added to a type and then scenarios are discovered later that require a collection of entity types. Take the following model with an entity type `application` that has a collection of `keyCredential`s: From 114fe124bb568a84bee4c5d2e2b86540ac205e4c Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:01:16 -0800 Subject: [PATCH 722/729] Update collections.md --- graph/articles/collections.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index 6fba8566..b96f4b6c 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -340,7 +340,7 @@ Take the following model with an entity type `application` that has a collection ``` and a scenario arises that requires, for example, to remove individual `keyCredential`s from the collection. -There are two options forward: //// TODO do we want to offer both options, or just one? +There are two options forward: ### 11.1 Side-by-side collection properties (for any collection of structural types) @@ -419,9 +419,6 @@ HTTP/1.1 400 Bad Request } ``` -TODO should this be a 409 conflict instead? -TODO implement this in WebApi - ### 11.2 Redefine as Entity Type (for collections of complex types) The model can be updated to simply switch the complex type for an entity type: @@ -474,5 +471,3 @@ GET /applications/{applicationId}?$select=keyCredentials 2. The default behavior for structural collections is to include them in the response payload for their containing entity. If this was the behavior of `application` before, it must be preserved by **auto-expanding** the `keyCredentials` property now that it is a navigation property (because the default behavior for navigation properties is to **not** expand them). 3. Structural collections can be updated using a `PATCH` request to the containing entity to replace the entire contents of the collection. If the service supported such updates to the structural collection, then updates to the new navigation property must preserve this behavior. - -TODO implement this in webapi From f0fdbd5ad97717ff86751cf7222a65616bf383ab Mon Sep 17 00:00:00 2001 From: Garrett DeBruin <16618938+corranrogue9@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:05:28 -0800 Subject: [PATCH 723/729] Update collections.md --- graph/articles/collections.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/graph/articles/collections.md b/graph/articles/collections.md index b96f4b6c..00276dbd 100644 --- a/graph/articles/collections.md +++ b/graph/articles/collections.md @@ -358,13 +358,13 @@ The model can be updated to have two collections side-by-side, deprecating the e + + + -+ ++ + + + + + -+ ++ @@ -372,7 +372,7 @@ The model can be updated to have two collections side-by-side, deprecating the e -+ ++ + + + @@ -382,17 +382,17 @@ The model can be updated to have two collections side-by-side, deprecating the e ``` Clients will now be able to refer to individual `keyCredential`s using `keyId` as a key, and they can now remove those `keyCredential`s using `DELETE` requests: ```http -DELETE /applications/{applicationId}/keyCredentialsAsEntities/{some_keyId} +DELETE /applications/{applicationId}/keyCredentials_v2/{some_keyId} ``` ```http HTTP/1.1 204 No Content ``` -While both properties exist on graph, the expectation is that `keyCredentials` and `keyCredentialsAsEntities` are treated as two "views" into the same data. +While both properties exist on graph, the expectation is that `keyCredentials` and `keyCredentials_v2` are treated as two "views" into the same data. To meet this expectation, workloads must: -1. Keep the properties consistent between `keyCredential` and `keyCredentialAsEntity`. +1. Keep the properties consistent between `keyCredential` and `keyCredential_v2`. Any changes to one type must be reflected in the other type. 2. Reject requests that update both collections at the same time. -A request that adds an item to `keyCredentialsAsEntities` while replacing the content of `keyCredentials` must rejected with a `400`, for example: +A request that adds an item to `keyCredentials_v2` while replacing the content of `keyCredentials` must rejected with a `400`, for example: ```http PATCH /applications/{applicationId} { @@ -402,7 +402,7 @@ PATCH /applications/{applicationId} "endDateTime": "2012-12-03T07:16:23Z" } ], - "keyCredentialsAsEntities@delta": [ + "keyCredentials_v2@delta": [ { "keyId": "20000000-0000-0000-0000-000000000000", "endDateTime": "2012-12-03T07:16:23Z" @@ -415,7 +415,7 @@ HTTP/1.1 400 Bad Request { "error": { "code": "badRequest", - "message": "'keyCredentials' and 'keyCredentialsAsEntities' cannot be updated in the same request.", + "message": "'keyCredentials' and 'keyCredentials_v2' cannot be updated in the same request.", } ``` From c2ef3c5cba631dd1d2bfcd6d88d69e7db944ca3e Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Fri, 13 Dec 2024 08:32:23 -0800 Subject: [PATCH 724/729] Initial pass of versioning guidelines (#564) * Initial pass of versioning guidelines * Apply suggestions from PR review Co-authored-by: Jeffrey Richter --- azure/VersioningGuidelines.md | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 azure/VersioningGuidelines.md diff --git a/azure/VersioningGuidelines.md b/azure/VersioningGuidelines.md new file mode 100644 index 00000000..7299e562 --- /dev/null +++ b/azure/VersioningGuidelines.md @@ -0,0 +1,51 @@ +# Azure Versioning Guidelines + +## History + +
    + Expand change history + +| Date | Notes | +| ----------- | -------------------------------------------------------------- | +| 2024-Nov-14 | Azure Service Versioning & Breaking Change Guidelines | + +
    + +## Guidelines + +This document provides a "Dos and Don'ts" list for complying with the Azure Versioning and Breaking Change Policy, +as documented [internally](aka.ms/AzBreakingChangesPolicy) and [externally](https://learn.microsoft.com/azure/developer/intro/azure-service-sdk-tool-versioning). + +:white_check_mark: **DO** thoroughly ensure/test the API contract is entirely correct before merging it into a production branch of the specs repo. + +Testing helps avoid "BugFix" changes to the API definition. Testing should be done at the HTTP level as well as through generated SDKs. + +:white_check_mark: **DO** retire all prior preview API versions 90 days after a new GA or preview API version is released. + +:white_check_mark: **DO** contact the Azure Breaking Change Review board to coordinate communications to customers +when releasing an API version requiring the retirement of a prior version. + +:white_check_mark: **DO** create a new preview API version for any features that should remain in preview following a new GA release. + +:white_check_mark: **DO** use a date strictly later than the most recent GA API version when releasing +a new preview API version. + +:white_check_mark: **DO** deprovision any API version that has been retired. Retired APIs versions should behave like +an unknown API version (see [ref](https://aka.ms/azapi/guidelines#versioning-api-version-unsupported)). + +:white_check_mark: **DO** remove retired API versions from the azure-rest-api-specs repo. + +:white_check_mark: **DO** review any change to service behavior that could disrupt customers with the Azure Breaking Changes review board, even if the change is not part of the API definition. + +Some examples of behavior changes that must be reviewed are: +- Introducing or changing rate limits to be more restrictive than previously +- Changing the permissions required to successfully execute an operation + +:no_entry: **DO NOT** change the behavior of an API version that is available to customers either in public preview or GA. +Changes in behavior should always be introduced in a new API version, with prior versions working as before. + +:no_entry: **DO NOT** introduce breaking changes from a prior GA version just to satisfy ARM or Azure API guidelines. + +Avoiding breaking changes in a GA API takes precedence over adherence to API guidelines and resolving linter errors. + +:no_entry: **DO NOT** keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction. From b2da320016c08bc07f1ff39b48e0d1fa365d0f31 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Thu, 9 Jan 2025 16:57:15 -0800 Subject: [PATCH 725/729] Fix RFC 1123 reference to 7231 Also makes how we refer to RFCs numerically in text. We had a mix of `RFC \d+` and `RFC\d+`. I wanted to make them consistent so they are easier (more consistent) to search. --- azure/Guidelines.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index dec9ef17..cf5f81b8 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -69,7 +69,7 @@ The Microsoft Azure Cloud platform exposes its APIs through the core building bl ### HTTP -Azure services must adhere to the HTTP specification, [RFC7231](https://tools.ietf.org/html/rfc7231). This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: +Azure services must adhere to the HTTP specification, [RFC 7231](https://tools.ietf.org/html/rfc7231). This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts: - [Uniform Resource Locators (URLs)](#uniform-resource-locators-urls) - [HTTP Request / Response Pattern](#http-request--response-pattern) @@ -120,7 +120,7 @@ https://-../... Examples: - Request URL: `https://blobstore.azure.net/contoso.com/account1/container1/blob2` -- Response header ([RFC2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4)): `content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2` +- Response header ([RFC 2557](https://datatracker.ietf.org/doc/html/rfc2557#section-4)): `content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2` - GUID format: `https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2` :white_check_mark: **DO** return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for `` or always a single verified domain. @@ -186,12 +186,12 @@ Because information in the service URL, as well as the request / response, are s Data type | Document that string must be --------- | ------- Boolean | true / false (all lowercase) -Integer | -253+1 to +253-1 (for consistency with JSON limits on integers [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259)) +Integer | -253+1 to +253-1 (for consistency with JSON limits on integers [RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259)) Float | [IEEE-754 binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) String | (Un)quoted?, max length, legal characters, case-sensitive, multiple delimiter -UUID | 123e4567-e89b-12d3-a456-426614174000 (no {}s, hyphens, case-insensitive) [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) -Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT [RFC7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) -Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ (with at most 3 digits of fractional seconds) [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) +UUID | 123e4567-e89b-12d3-a456-426614174000 (no {}s, hyphens, case-insensitive) [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122) +Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT [RFC 7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) +Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ (with at most 3 digits of fractional seconds) [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) Byte array | Base-64 encoded, max length Array | One of a) a comma-separated list of values (preferred), or b) separate `name=value` parameter instances for each value of the array @@ -209,7 +209,7 @@ If-Match | Request | "67ab43" or * (no quotes) (see [Conditional R If-None-Match | Request | "67ab43" or * (no quotes) (see [Conditional Requests](#conditional-requests)) If-Modified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#conditional-requests)) If-Unmodified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see [Conditional Requests](#conditional-requests)) -date | Both | Sun, 06 Nov 1994 08:49:37 GMT (see [RFC7231, Section 7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2)) +date | Both | Sun, 06 Nov 1994 08:49:37 GMT (see [RFC 7231, Section 7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2)) _content-type_ | Both | application/merge-patch+json _content-length_ | Both | 1024 _x-ms-request-id_ | Response | 4227cdc5-9f48-4e84-921a-10967cb785a0 @@ -227,9 +227,9 @@ retry-after | Response | 180 (see [RFC 7231, Section 7.1.3](https://da :white_check_mark: **DO** compare request header values using case-sensitivity if the header name requires it -:white_check_mark: **DO** accept date values in headers in HTTP-Date format and return date values in headers in the IMF-fixdate format as defined in [RFC7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT". +:white_check_mark: **DO** accept date values in headers in HTTP-Date format and return date values in headers in the IMF-fixdate format as defined in [RFC 7231, Section 7.1.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1), e.g. "Sun, 06 Nov 1994 08:49:37 GMT". -Note: The RFC 7321 IMF-fixdate format is a "fixed-length and single-zone subset" of the RFC 1123 / RFC 5822 format, which means: a) year must be four digits, b) the seconds component of time is required, and c) the timezone must be GMT. +Note: The RFC 7231 IMF-fixdate format is a "fixed-length and single-zone subset" of the RFC 1123 / RFC 5822 format, which means: a) year must be four digits, b) the seconds component of time is required, and c) the timezone must be GMT. :white_check_mark: **DO** create an opaque value that uniquely identifies the request and return this value in the `x-ms-request-id` response header. @@ -281,7 +281,7 @@ Because of this, required fields can only be introduced in the 1st version of a :white_check_mark: **DO** use GET for resource retrieval and return JSON in the response body -:white_check_mark: **DO** create and update resources using PATCH [RFC5789] with JSON Merge Patch [(RFC7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. +:white_check_mark: **DO** create and update resources using PATCH [RFC 5789] with JSON Merge Patch [(RFC 7396)](https://datatracker.ietf.org/doc/html/rfc7396) request body. :white_check_mark: **DO** use PUT with JSON for wholesale create/replace operations. **NOTE:** If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT). @@ -401,19 +401,19 @@ Services, and the clients that access them, may be written in multiple languages :white_check_mark: **DO** use integers within the acceptable range of JSON number. -:white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC3339 for date/time. +:white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC 3339 for date/time. -:white_check_mark: **DO** use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC3339 for date/time. +:white_check_mark: **DO** use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC 3339 for date/time. :white_check_mark: **DO** ensure that information exchanged between your service and any client is "round-trippable" across multiple programming languages. -:white_check_mark: **DO** use [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) for date/time. +:white_check_mark: **DO** use [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) for date/time. :white_check_mark: **DO** use a fixed time interval to express durations e.g., milliseconds, seconds, minutes, days, etc., and include the time unit in the property name e.g., `backupTimeInMinutes` or `ttlSeconds`. -:heavy_check_mark: **YOU MAY** use [RFC3339 time intervals](https://wikipedia.org/wiki/ISO_8601#Durations) only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P3M" represents 3 months no matter how many days between the start and end dates, or "P1Y" represents 366 days on a leap year. The value must be round-trippable. +:heavy_check_mark: **YOU MAY** use [RFC 3339 time intervals](https://wikipedia.org/wiki/ISO_8601#Durations) only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P3M" represents 3 months no matter how many days between the start and end dates, or "P1Y" represents 366 days on a leap year. The value must be round-trippable. -:white_check_mark: **DO** use [RFC4122](https://datatracker.ietf.org/doc/html/rfc4122) for UUIDs. +:white_check_mark: **DO** use [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122) for UUIDs. :heavy_check_mark: **YOU MAY** use JSON objects to group sub-fields together. @@ -1141,7 +1141,7 @@ Designing an API for accessing a single file, depending on your scenario, is rel :white_check_mark: **DO** allow the customer to specify a URL path to a single Storage object if your service requires access to a single file. -:heavy_check_mark: **YOU MAY** allow the customer to provide a [last-modified](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2) timestamp (in RFC1123 format) for read-only files. This allows the client to specify exactly which version of the files your service should use. +:heavy_check_mark: **YOU MAY** allow the customer to provide a [last-modified](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2) timestamp (in RFC 7231 format) for read-only files. This allows the client to specify exactly which version of the files your service should use. When reading a file, your service passes this timestamp to Azure Storage using the [if-unmodified-since](https://datatracker.ietf.org/doc/html/rfc7232#section-3.4) request header. If the Storage operation fails with 412, the Storage object was modified and your service operation should return an appropriate 4xx status code and return the Storage error in your operation's 'inner-error' (see guideline above). :white_check_mark: **DO** allow the customer to specify a URL path to a logical folder (via prefix and delimiter) if your service requires access to multiple files (within this folder). For more information, see [List Blobs API](https://docs.microsoft.com/rest/api/storageservices/list-blobs) From bbd552f93c6d7282c45d92637b5dc99d582a3328 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 28 Mar 2025 11:38:11 -0700 Subject: [PATCH 726/729] Update Guidelines.md Added guidelines about JSON ID and null values --- azure/Guidelines.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index cf5f81b8..ce2c893d 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -16,6 +16,7 @@ Please ensure that you add an anchor tag to any new guidelines that you add and | Date | Notes | | ----------- | -------------------------------------------------------------- | +| 2025-Mar-28 | Added guidelines about JSON ID and null values | | 2024-Mar-17 | Updated LRO guidelines | | 2024-Jan-17 | Added guidelines on returning string offsets & lengths | | 2023-May-12 | Explain service response for missing/unsupported `api-version` | @@ -389,7 +390,9 @@ Example: :white_check_mark: **DO** treat JSON field names with case-sensitivity. -:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions (e.g. GUIDs) but avoid if at all possible. +:white_check_mark: **DO** treat JSON field values with case-sensitivity. There may be some exceptions but avoid if at all possible. + +:white_check_mark: **DO** treat JSON field value representing a unique ID as an opaque string value and compare them with case-sensitivity. For example, IDs are frequently [UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier), [CUIDs](https://github.com/paralleldrive/cuid2), [Nano ID](https://blog.openapihub.com/en-us/what-is-nano-id-its-difference-from-uuid-as-unique-identifiers/), or other formats. The choice of ID format is a service implementation detail. Customer code should only ever need to get, store, and send these values and should never perform any other kind of parsing or interpretation of ID values. Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types: @@ -399,9 +402,13 @@ Services, and the clients that access them, may be written in multiple languages Number | Signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) String | Used for everything else +:white_check_mark: **DO NOT** send JSON fields with a null value from the service to the client. Instead, the service should just not send this field at all (this reduces payload size). Semantically, Azure services treat a missing field and a field with a value of null as identical. + +:white_check_mark: **DO** accept JSON fields with a null value only for a PATCH operation with a JSON Merge Patch payload. A field with a value of null instructs the service to delete the field. If the field cannot be deleted, then return 400-BadRequest, else return the resource with the deleted field missing from the response payload (see bullet above). + :white_check_mark: **DO** use integers within the acceptable range of JSON number. -:white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC 3339 for date/time. +:white_check_mark: **DO** establish a well-defined contract for the format of strings. For example, determine minimum length, maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC 3339 for date/time. :white_check_mark: **DO** use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC 3339 for date/time. From b8f9c8cc249671067b0d1008de216dea959c6744 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 28 Mar 2025 11:41:24 -0700 Subject: [PATCH 727/729] Update Guidelines.md Remove null from JSON payload --- azure/Guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index ce2c893d..03d84e6d 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -554,10 +554,10 @@ NOTE: It is a breaking change to add paging in the future ```json { "value": [ - { "id": "Item 01", "etag": "\"abc\"", "price": 99.95, "sizes": null }, + { "id": "Item 01", "etag": "\"abc\"", "price": 99.95, "size": "Medium" }, { … }, { … }, - { "id": "Item 99", "etag": "\"def\"", "price": 59.99, "sizes": null } + { "id": "Item 99", "etag": "\"def\"", "price": 59.99, "size": "Large" } ], "nextLink": "{opaqueUrl}" } From 3db3462a9361132adf60da74cff3aced08eb9ff7 Mon Sep 17 00:00:00 2001 From: Charles Lowell <10964656+chlowell@users.noreply.github.com> Date: Wed, 2 Apr 2025 11:47:03 -0700 Subject: [PATCH 728/729] Correct symbol for "DO NOT" item (#568) --- azure/Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index 03d84e6d..57df5a64 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -402,7 +402,7 @@ Services, and the clients that access them, may be written in multiple languages Number | Signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) String | Used for everything else -:white_check_mark: **DO NOT** send JSON fields with a null value from the service to the client. Instead, the service should just not send this field at all (this reduces payload size). Semantically, Azure services treat a missing field and a field with a value of null as identical. +:no_entry: **DO NOT** send JSON fields with a null value from the service to the client. Instead, the service should just not send this field at all (this reduces payload size). Semantically, Azure services treat a missing field and a field with a value of null as identical. :white_check_mark: **DO** accept JSON fields with a null value only for a PATCH operation with a JSON Merge Patch payload. A field with a value of null instructs the service to delete the field. If the field cannot be deleted, then return 400-BadRequest, else return the resource with the deleted field missing from the response payload (see bullet above). From 52fb70f10f62d715f2d8faccf80a76d2b1e4f2f5 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Mon, 11 May 2026 20:42:50 -0400 Subject: [PATCH 729/729] Add nullable enum guidance: null vs none sentinel values --- graph/patterns/enums.md | 66 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/graph/patterns/enums.md b/graph/patterns/enums.md index 080435af..47538134 100644 --- a/graph/patterns/enums.md +++ b/graph/patterns/enums.md @@ -102,6 +102,72 @@ Additionally speaking, depending on the situation, a nullable enum can very like If used, `EnumType` names should be singular if the are non-flags enums, and the names should be plural if they are flags enums. + +#### Nullable enums + +Enum properties can be marked `Nullable="true"`, which means the property can hold `null` in addition to any defined member. +Before making an enum property nullable, consider whether a sentinel member like `none` better communicates the intent. + +##### `null` vs `none` + +| Value | Meaning | Use when | +|---|---|---| +| `null` | The property has no value — it was never set or is not applicable | The absence of a value is semantically different from every defined member | +| `none` | An explicit "nothing selected" choice within the enum's domain | "No selection" is a valid, intentional state the caller can set | + +> **Note:** The `unknownFutureValue` sentinel is always required as the last known member of every enum (see [evolvable enums](./evolvable-enums.md)). +> It is unrelated to nullability and must be present regardless of whether the property is nullable or uses a `none` member. + +##### Prefer a `none` member over nullable + +In most cases, add a `none` member (value `0`) instead of making the property nullable. +This keeps the property non-nullable, which is simpler for SDK consumers and avoids the three-way ambiguity of "is it null, is it none, or is it a real value?" + +```xml + + + + + + + + + + +``` + +##### When nullable is appropriate + +Use `Nullable="true"` on an enum property only when **all** of the following apply: + +1. **Absence is meaningful** — `null` represents "not set" or "not applicable," which is semantically distinct from every enum member including a hypothetical `none`. +2. **A sentinel member would be misleading** — adding `none` would imply the caller actively chose "nothing," but the actual semantics are that the property doesn't apply to this instance. +3. **The property is optional on creation** — the service does not assign a default value; `null` is the expected state until the caller explicitly sets one. + +```xml + + +``` + +##### Anti-pattern: nullable + `none` + +Do **not** combine a nullable enum with a `none` member. +This creates two ways to express "no value" and forces callers to handle both `null` and `none`, leading to inconsistency and bugs. + +```xml + + + + + + + + + +``` + +Pick one: either `none` with `Nullable="false"`, or no `none` member with `Nullable="true"`. + #### Flag Enums or Collection of Enums In case an enum can have multiple values at the same time the tentation is to model the property as a collection of Enums:

    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 diff --git a/graph/modeling-variants.md b/graph/modeling-variants.md deleted file mode 100644 index 76ffebee..00000000 --- a/graph/modeling-variants.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: Modeling variants -owner: chrispre ---- - -# Modeling Variants - -Frequently we encounter situations where a certain piece of data in Microsoft Graph comes in different variants. Depending on the situation we call these variants, kinds, types, etc.. Some examples are - -- owners of groups can be either a `user` or a `servicePrincipal`. -- an approver of a request can be a single user, a group, etc.. -- the end of a recurring event can be after a number of repetitions or at a certain date, or never. - -All these variants have different properties representing the information needed in these cases. - -OData and Microsoft Graph offer different ways to model the API and these different variants. We'll describe those here, and list the advantages and disadvantages of each modeling technique. - -In the remainder of the document we are using the term "variant" instead of "kind", "flavor", "type". "type" is defined by OData and we do not want to presume there has to be a type per variant. - -## Approaches - -There are different approaches to design a model in situations with multiple variants of common concept. We are going to compare three common patterns that we see in Microsoft Graph today. - -### Type Hierarchy - -A shallow **type hierarchy**: One abstract base type with a few common properties and one sub-type for each variant. OData adds `@odata.type` properties to the JSON representation when instances of these types are returned so that a client can quickly distinguish them. - -One prominent example is the base type [graph.outlookItem](https://docs.microsoft.com/en-us/graph/api/resources/outlookitem?view=graph-rest-1.0) with subtypes like [message](https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0), [contact](https://docs.microsoft.com/en-us/graph/api/resources/contact?view=graph-rest-1.0), [event](https://docs.microsoft.com/en-us/graph/api/resources/event?view=graph-rest-1.0). - -### Facets - -A single entity type with **facets**: One type in the schema with common properties and one property (of complex type) per variant. The facet properties only have a value when the object represents that variant. - -This can be seen for example in [driveItem](https://docs.microsoft.com/en-us/graph/api/resources/driveitem?view=graph-rest-1.0) where there are four variants (folder, file, image, photo) and one property per variant with the same name. These properties are modeled as a complex types that holds all information for that specific facet/variant. (e.g. just the `element count` property for folder and eight different properties for photo like camera model and settings). - -### Flat - -A **flat** bag of properties: One entity type with all the potential properties plus an additional property to distinguish the variants, often called `type`. The `type` property describes the variant and also defines properties are required/meaningful for the variant given by the `type` property. - -Since the name `type` could be confused with the notion of type in OData, it is often recommended to qualify the property name. E.g. `recurrenceType` instead of `type`. - -A good example for this is the recurrencePattern and recurrenceRange types (both properties on [patternedRecurrence](https://docs.microsoft.com/en-us/graph/api/resources/patternedrecurrence?view=graph-rest-1.0)). -The recurrencePattern has 6 variants expressed as 6 different values of the `type` property (e.g. daily, weekly, ...). -The key here is that for each of these values, some properties are meaningful and others are ignored. (e.g. `daysOfWeek` is relevant when `type` is `weekly` but not when it is `daily`). - -## Pros and Cons - -Below are a few pros and cons to decide which pattern to use. - -- In **[hierarchy](#type-hierarchy)**, the interdependencies of properties, i.e. which properties are relevant for which variants, is fully captured in metadata and client code can potentially leverage that to construct and/or validate requests. -- Introducing new cases in **[hierarchy](#type-hierarchy)** is relatively isolated (which is why it is so familiar to OOP) and is considered backwards compatible (at least syntactically). But see the note about [changing semantics](#semantics) below. -- Introducing new cases/variants in **[facets](#facets)** is straightforward. One needs to be careful since it can introduce situations where previously exactly one of the facets was non-null and now all the old ones are null. For example imagine a new facet "shortcut" is added to the example above where everything was one of folder,file,image,photo. Adding the shortcut facet means that there are now object with all of the previous four are null. - This is not unlike adding new subtypes in the hierarchy pattern or adding a new type value in the flat pattern. -- **[hierarchy](#type-hierarchy)** and **[facets](#facets)** (to a slightly lesser degree) are well suited for strongly typed client programming languages. Whereas **[flat](#flat)** is more familiar to developers of less strongly typed languages. -- **[facets](#facets)** has the potential to model what is typically associated with multiple inheritance (but it is not inheritance so please don’t quote me). Just to illustrate the point and constructing a highly hypothetical scenario, in the OneDrive example, having an item be a folder and a photo is easy to represent. -- **[facets](#facets)** and **[flat](#flat)** lend to syntactically simpler filter query expression. **[hierarchy](#type-hierarchy)** is more explicit but requires the less well known cast segments in the filter query. For example, if one wants to filter on the importance of a mail in a collection of outlookItems, one first needs to "cast" to mailItem to then filter on the importance property: `$filter=microsoft.graph.mailItem/importance eq 'High'`. -- **[flat](#flat)** might resemble a structure that developers are familiar with from on-prem products and their API (e.g. recurrence in Microsoft Graph is modeled after Exchange Server's model). Even though the Graph API can and should abstract from the implementation details this can have benefits in documentation and adoption. -- **[hierarchy](#type-hierarchy)** can become hard to maintain if the base type is quite abstract and the hierarchy is relatively wide. Lets assume a situation where collections are modeled using the base type with many sub-types, but the actual elements of the collection are only ever one or two of the sub-types. When a new subtype gets introduced and the collection(s) quickly contain elements of this new sub-type, client code has to react to these changes. It is important to check if this changes the semantics of the property (actual or assumed). See also [changing semantics](#semantics) below. -- Even though not frequently used in Microsoft Graph, **[hierarchy](#type-hierarchy)** can be refined by annotating the collections with OData `derived type constraints` (see [validation vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Validation.V1.md)). This annotation restricts the values to certain sub-trees of an inheritance hierarchy. It makes it very explicit that the collection only contains elements of some of the subtypes and helps to not return object of a type that is semantically not suitable. - -## Future - -The OData team is looking for feedback what is missing in terms of modeling tools and expressiveness that can help making these design decisions. - -One of the options to explore that helps address some of the cons with overly broad subtype hierarchy is the OData annotation term `MayImplement` (see [Core vocabulary](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md)), a feature that is not yet implemented in the OData libraries or Microsoft Graph. - -The `MayImplement` annotation is defined as - -> A collection of qualified type names outside of the type hierarchy that instances of this type might be addressable as by using a type-cast segment - -This would allow to keep the type hierarchy narrow but still have some objects cast to a type outside that hierarchy. Please contact us to discuss if this could be helpful for your scenario. - -## Summary - -As can be seen in a few of the Pros and Cons, one of the important aspects discussed here, is that the API design goes beyond the syntactical aspects of the API and it is important to plan ahead how the API evolves, lay the foundation, and allow the users to form a good understanding of the semantics of the API. **Changing the semantics is always a breaking change**. The different modeling patterns as described above, differ in how they express syntax and semantic and how they allow the API to evolve without breaking compatibility. diff --git a/graph/naming-conventions.md b/graph/naming-conventions.md deleted file mode 100644 index 8a04f3cb..00000000 --- a/graph/naming-conventions.md +++ /dev/null @@ -1,119 +0,0 @@ - -### General Guidelines - -::: tip ✔ DO use `lowerCamelCase` for _all_ names. - -- Right: `automaticRepliesStatus`. -- Wrong: `kebab-case` or `snake_case`. - -::: - -::: warning ✖ AVOID redundant words in names. - -- Right: `/places/{id}/`**_type_** and `/phones/{id}/`**_number_** -- Wrong: `/places/{id}/`_**placeType**_ and `/phones/{id}/`**_phoneNumber_** - -::: - -::: warning ✖ AVOID using brand names in type or property names. - -- Right: `chat` -- Wrong: `teamsChat` - -::: - -::: warning ✖ AVOID using acronyms or abbreviations unless they are broadly understood. - -- Right: `url` or `htmlSignature` -- Wrong: `msodsUrl` or `dlp` - -::: - -::: tip ✔ DO use singular nouns for type names. - -- Right: `address` -- Wrong: `addresses` - -::: - -::: tip ✔ DO use plural nouns for collections (for listing a type or collection properties). - -- Right: `addresses` -- Wrong: `address` - -::: - -::: tip ✔ DO pluralize the noun even when followed by an adjective (a "postpositive"). - -- Right: `passersby` or `mothersInLaw` -- Wrong: `notaryPublics` or `motherInLaws` - -::: - -### Casing - -::: tip ✔ DO case two-letter acronyms with the same case. - -- Right: `ioLimit` or `totalIOAmount` -- Wrong: `iOLimit` or `totalIoAmount` - -::: - -::: tip ✔ DO case three+ letter acronyms the same as a normal word. - -- Right: `fidoKey` or `oauthUrl` -- Wrong: `webHTML` - -::: - -::: danger ✖ DO NOT capitalize the word following a prefix or words within a compound word. - -- Right: `subcategory`, `geocoordinate` or `crosswalk` -- Wrong: `metaData`, `semiCircle` or `airPlane` - -::: - -::: tip ✔ DO capitalize within hyphenated and open (spaced) compound words. - -- Right: `fiveYearOld`, `daughterInLaw` or `postOffice` -- Wrong: `paperclip`, `changingroom` or `fullmoon` - -::: - -### Prefixes and Suffixes - -::: tip ✔ DO suffix date and time properties. - -- Right: `dueDate`—an `Edm.Date` -- Right: `createdDateTime`—an `Edm.DateTimeOffset` -- Right: `recurringMeetingTime`—an `Edm.TimeOfDay` -- Wrong: `dueOn` or `startTime`, both an `Edm.DateTimeOffset` - -::: - -::: danger ✖ DO NOT suffix property names with primitive type names unless the type is temporal. - -- Right: `isEnabled` or `amount` -- Wrong: `enabledBool` - -::: - -::: tip ✔ DO prefix property names for properties concerning a different entity. - -- Right: `siteWebUrl` on `driveItem`, or `userId` on `auditActor` -- Wrong: `webUrl` on `contact` when its the `companyWebUrl` - -::: - -### Common property names - -| Approved name | Type | Use | -| ---------------------- | -------------- | --------------------------------------------------------- | -| `displayName` | String | A label that can be displayed or read aloud. Not `name`. | -| `webUrl` | String | The web page for viewing or editing this entity. | -| `url` | String | A URL to a resource. (In Graph often holds the `webUrl`.) | -| `lastModifiedDateTime` | DateTimeOffset | The last time this entity changed. | -| `createdDateTime` | DateTimeOffset | The time this entity was created. | -| `createdBy` | identitySet | The creator of this entity. | -| `createdByUser` | user | The user in `/users` who created this entity. | - From 16fb9aba05eb424ede1df829d93a08bfcba4cd7e Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 5 Jan 2022 15:23:29 -0500 Subject: [PATCH 247/729] deprecation updates --- graph/GuidelinesGraph.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index aa32723d..13f20703 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -358,7 +358,7 @@ For a complete mapping of error codes to HTTP statuses you can refer to the Microsoft Graph definition of breaking changes is based on the [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/graph/Guidelines.md#123-definition-of-a-breaking-change). -In general, making changes to the API contract for existing elements is +In general, making all but additive changes to the API contract for existing elements is considered breaking. Adding new elements is allowed and not considered a breaking change. @@ -390,11 +390,12 @@ breaking change. ### Versioning and Deprecation -As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If it is not possible then you must version elements of your APIs. +As the market and technology evolves your APIs will require modifications in this case you must avoid breaking changes and add new resources and features incrementally. If that is not possible then you must version elements of your APIs. Microsoft Graph allows versioning of elements including entities and properties. The versioning process goes along with deprecation and as soon as you introduce a new element update the previous version needs to follow the deprecation process. You must create a new version of your element for any breaking change and name it uniquely. -If the current element name is best, a new element is added that has the existing name plus the suffix _v2, indicating that it is the successor to the original. The original element is then marked as deprecated using annotations. +In some cases, the API will have evolved such that there is a new, natural unique name. In other cases, the original name may still be the most descriptive for the evolved element. In the latter case, the suffix _v2 must be added to the original name to make it unique. +The original element is then marked as deprecated using annotations. Microsoft Graph provides two public endpoints to support API lifecycle: 1. API sets on the v1.0 endpoint (https://graph.microsoft.com/v1.0) are in general availability (GA) status. @@ -417,17 +418,10 @@ Recommended API Design patterns: | Pattern | Description | Reference | |-------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| -| Shared Type | The ability to reuse a type defined by another service. | TBD | | Type Hierarchy | The ability to model parent-child relationships using subtypes. | [Modeling with Subtypes](./Modelling%20with%20Subtypes%20Pattern.md) | | Facets | The ability to model parent-child relationships using Facet pattern. | [Modeling with Facets](./Modelling%20with%20Subtypes%20Pattern.md) | -| Dictionary | The ability to persist a variable number of properties. | TBD | -| Evolvable Enums | The ability to enable non-breaking changes for Enum type. | TBD | -| Change Tracking | The ability to get notified (push) when a change occurs in the data exposed by Microsoft Graph | TBD | -| Long Running Operations | The ability to model asynchronous operations. | TBD | -| Delta Queries | The ability to query changes in the data exposed by Microsoft Graph | TBD | -| Navigation Properties | | TBD | -| Viewpoint | | TBD | -|Property projection $select| |TBD + | + ## References From 102fe9b084165c36a95c3340b3b03f7b6009cbb1 Mon Sep 17 00:00:00 2001 From: OlgaPodo Date: Wed, 5 Jan 2022 15:23:39 -0500 Subject: [PATCH 248/729] deprecation --- graph/deprecation.md | 42 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/graph/deprecation.md b/graph/deprecation.md index 549f751e..9c488446 100644 --- a/graph/deprecation.md +++ b/graph/deprecation.md @@ -1,30 +1,18 @@ -### Deprecation Process +### Deprecation Guidelines -If your API requires an introduction of breaking changes you must follow the -deprecation process: +If your API requires the introduction of breaking changes you must add Revisions annotations to the API definition with the following terms: -- After API review board approvals, add Revisions annotation to the API - definition CSDL with the following terms: + + - Date: Date when the element was marked as deprecated. + - Version: Used to organize the ChangeLog. Use the format "YYYY-MM/Category" where "YYYY-MM" is the month the deprecation is announced, and "Category" is the category under which the change is described. + - Kind: Deprecated - + - Description: Human readable description of the change: Used in changelog, documentation etc. + - RemovalDate: Earliest date when the element may be removed. - - Kind of change: Deprecated (vs "added" to track added properties/types) - - - Human readable description of the change: Used in changelog, - documentation etc. - - - Version: Used to identify group of changes. Of the format - "YYYY-MM/Category" where "YYYY-MM" is the month the deprecation is - announced, and "Category" is the category under which the change is - described in the ChangeLog - - - Date: Date when the element was marked as deprecated - - - RemovalDate: Date when the element may be removed - -The annotation can be applied to a type, entity set, singleton, property, -navigation property, function or action. If a type is marked as deprecated, it -is not necessary to mark members of that type as deprecated, nor is it necessary -to annotate any usage of that type in entity sets, singletons, properties, -navigation properties, functions, or actions. +The annotation can be applied to a type, an entity set, a singleton,a property, a +navigation property, a function or an action. If a type is marked as deprecated, it +is not necessary to mark the members of that type as deprecated, nor is it necessary +to annotate any usages of that type. **Example of property annotation:** @@ -45,12 +33,10 @@ navigation properties, functions, or actions. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When the request URL contains a reference to a deprecated model element, the -HTTP response includes a [Deprecation +When the request URL contains a reference to a deprecated model element, the gateway will add a [Deprecation header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the date the element was marked as deprecated) and a Sunset header (with the date 2 -years beyond the Deprecation date). Response also includes a link header -pointing to the breaking changes page. +years beyond the Deprecation date) to the response. **Deprecation header example:** From 82c1251686d8a7188bd8d9704b5c1a3b1fbb985e Mon Sep 17 00:00:00 2001 From: "Johan Stenberg (MSFT)" Date: Wed, 12 Jan 2022 14:57:37 +0100 Subject: [PATCH 249/729] Update Guidelines.md Address review feedback: clarify logging vs. tracing, remove x-ms-return-client-request-id header guidance. --- azure/Guidelines.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/azure/Guidelines.md b/azure/Guidelines.md index f3ab903f..c0eae686 100644 --- a/azure/Guidelines.md +++ b/azure/Guidelines.md @@ -183,7 +183,6 @@ _content-type_ | Both | application/merge-patch+json _content-length_ | Both | 1024 _x-ms-request-id_ | Response | 4227cdc5-9f48-4e84-921a-10967cb785a0 (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) _x-ms-client-request-id_| Both | 227cdc5-9f48-4e84-921a-10967cb785a1 (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) -_x-ms-return-client-request-id_| Request | true (see [Distributed Tracing & Telemetry](#Distributed-Tracing--Telemetry)) ETag | Response | "67ab43" (see [Conditional Requests](#Conditional-Requests)) last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT _x-ms-error-code_ | Response | (see [Handling Errors](#Handling-Errors)) @@ -999,9 +998,8 @@ In addition to distributed tracing, Azure also uses a set of common correlation |Name |Applies to|Description| |-----------------------------|----------|-----------| -|x-ms-client-request-id. |Both |Optional. Caller-specified value identifying the request, in the form of a GUID with no decoration such as curly braces (e.g. `x-ms-client-request-id: 9C4D50EE-2D56-4CD3-8152-34347DC9F2B0`). If the caller provides this header the service **must** log this with their traces to facilitate tracing a single request. Because this header can be client-generated, it should not be assumed to be unique by the service implementation. -|x-ms-return-client-request-id|Request. |Optional. If specified, the service **must** include a `x-ms-client-request-id` header with the corresponding value in the response. Allows clients to correlate responses with requests. -|x-ms-request-id |Response |Required. Service generated correlation id identifying the request, in the form of a GUID with no decoratoin such as curly braces. In contrast to the the `x-ms-client-request-id`, the service **must** ensure that this valu is globally unique. Services should log this value with their traces to facilitate tracing of a single request. +|x-ms-client-request-id |Both |Optional. Caller-specified value identifying the request, in the form of a GUID with no decoration such as curly braces (e.g. `x-ms-client-request-id: 9C4D50EE-2D56-4CD3-8152-34347DC9F2B0`). If the caller provides this header the service **must** include this in their log entries to facilitate correlation of log entries for a single request. Because this header can be client-generated, it should not be assumed to be unique by the service implementation. +|x-ms-request-id |Response |Required. Service generated correlation id identifying the request, in the form of a GUID with no decoration such as curly braces. In contrast to the the `x-ms-client-request-id`, the service **must** ensure that this value is globally unique. Services should log this value with their traces to facilitate correlation of log entries for a single request. ## Final thoughts These guidelines describe the upfront design considerations, technology building blocks, and common patterns that Azure teams encounter when building an API for their service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success. From 59bb74f2d8cd5d2d7bd535108e02e4c4df56a8b5 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 18 Jan 2022 15:17:18 -0500 Subject: [PATCH 250/729] Update graph/GuidelinesGraph.md Co-authored-by: Gareth Jones --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 13f20703..4f6a6fac 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -32,7 +32,7 @@ Table of Contents When building a digital ecosystem API usability becomes a business priority. Success of your ecosystem depends on APIs that are easy to discover, simple to use, fit for purpose, and consistent across your products. -This document offers guidance that Microsoft Graph API developer teams MUST follow to +This document offers guidance that Microsoft Graph API producer teams MUST follow to ensure that Microsoft Graph has a consistent and easy to use API surface. A new API design should meet the following goals: From b1c99fbce87da54f67a0083d13ed1cfdf24476f5 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 18 Jan 2022 15:17:34 -0500 Subject: [PATCH 251/729] Update graph/GuidelinesGraph.md Co-authored-by: Darrel --- graph/GuidelinesGraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/GuidelinesGraph.md b/graph/GuidelinesGraph.md index 4f6a6fac..e13305d8 100644 --- a/graph/GuidelinesGraph.md +++ b/graph/GuidelinesGraph.md @@ -376,7 +376,7 @@ breaking change. - Changes to the URL or fundamental request/response associated with a resource -- Removal, rename, or change to the type of a declared property +- Removal, rename, or change to an incompatible type of a declared property - Removal or rename of APIs or API parameters - Addition of a required request header - Addition of a EnumType members for non-evolvable enumerations From 02f4aa8b3f2ccd769eaf9683a81347e59a9f6856 Mon Sep 17 00:00:00 2001 From: OlgaPodo <86168392+OlgaPodo@users.noreply.github.com> Date: Tue, 18 Jan 2022 15:20:40 -0500 Subject: [PATCH 252/729] First draft of the Graph guidelines (#303) * test commit * Create Modelling with Subtypes Pattern.md * Fixed formatting * Check boxes * Need formatting * format testing * formatting and TOC * formatting * Legend * Formatting with icons * Fixing legend * Cleaning * Combine behavior rules * formatting * Editing and alignment * reordering * Added formatting and patterns * Added TOC * Update graph/GuidelinesGraph.md Co-authored-by: Darrel * formatting * moving to RFC 2119 conventions * resource modeling * icon updates * domain model image * whiteboard picture * cropped picture * cropped picture * generic context * moved deprecation to standalone md * formatting * template update * grammar * first statement * added finctions and actions * Updated TOC * editing * added entity and complex * type hierarchy * patterns * facets pattern * example * examples * diagram update * Update graph/Modelling with Facets Pattern.md Co-authored-by: Darrel * Update graph/Modelling with Facets Pattern.md Co-authored-by: Darrel * Update graph/Modelling with Facets Pattern.md Co-authored-by: Darrel * Update graph/Modelling with Facets Pattern.md Co-authored-by: Darrel * Update graph/GuidelinesGraph.md Co-authored-by: Darrel * removed Odata from breaking changes list * fix for complex types * versionsning * updates * added relationships * added POST example * Approach update * applied Gareth's comments * Update graph/GuidelinesGraph.md Co-authored-by: Darrel * corrections * updates * removed variants and naming conv * deprecation updates * deprecation * Update graph/GuidelinesGraph.md Co-authored-by: Gareth Jones * Update graph/GuidelinesGraph.md Co-authored-by: Darrel Co-authored-by: Darrel Co-authored-by: Gareth Jones --- graph/DomainModel.png | Bin 0 -> 150674 bytes graph/GuidelinesGraph.md | 439 ++++++++++++++++++ graph/ModelExample.png | Bin 0 -> 49988 bytes graph/Modelling with Facets Pattern.md | 111 +++++ graph/Modelling with Flat Bag Pattern.md | 131 ++++++ graph/Modelling with Subtypes Pattern.md | 138 ++++++ graph/PatternDescriptionTemplate.md | 44 +- graph/ProtocolRuleset.md | 178 ------- ...{guidelines.md => SummaryOfGraphDeltas.md} | 0 graph/deprecation.md | 47 ++ graph/modeling-variants.md | 75 --- graph/naming-conventions.md | 119 ----- graph/~$delling with Facets Pattern.md | Bin 0 -> 162 bytes graph/~$delling with Subtypes Pattern.md | Bin 0 -> 162 bytes graph/~WRL0034.tmp | 134 ++++++ 15 files changed, 1020 insertions(+), 396 deletions(-) create mode 100644 graph/DomainModel.png create mode 100644 graph/GuidelinesGraph.md create mode 100644 graph/ModelExample.png create mode 100644 graph/Modelling with Facets Pattern.md create mode 100644 graph/Modelling with Flat Bag Pattern.md create mode 100644 graph/Modelling with Subtypes Pattern.md delete mode 100644 graph/ProtocolRuleset.md rename graph/{guidelines.md => SummaryOfGraphDeltas.md} (100%) create mode 100644 graph/deprecation.md delete mode 100644 graph/modeling-variants.md delete mode 100644 graph/naming-conventions.md create mode 100644 graph/~$delling with Facets Pattern.md create mode 100644 graph/~$delling with Subtypes Pattern.md create mode 100644 graph/~WRL0034.tmp diff --git a/graph/DomainModel.png b/graph/DomainModel.png new file mode 100644 index 0000000000000000000000000000000000000000..869206bfc8cbd2ae739b84ba605c22de25fc77a0 GIT binary patch literal 150674 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