Skip to content

Commit b7e0d95

Browse files
committed
Restructure and strengthen guidance on conditional requests
1 parent 313753e commit b7e0d95

File tree

2 files changed

+58
-28
lines changed

2 files changed

+58
-28
lines changed

azure/ConsiderationsForServiceDesign.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ Before releasing your API plan to invest significant design effort, get customer
172172
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.
173173
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.
174174

175-
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.
175+
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.
176176
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.
177177

178178
## Avoid Surprises
@@ -234,7 +234,7 @@ PATCH must never be used for long-running operations -- it should be reserved fo
234234
If a long-running update is required it should be implemented with POST.
235235

236236
There is a special form of long-running operation initiated with PUT that is described
237-
in [Create (PUT) with additional long-running processing](#create-put-with-additional-long-running-processing).
237+
in [Create (PUT) with additional long-running processing](./Guidelines.md#put-operation-with-additional-long-running-processing).
238238
The remainder of this section describes the pattern for long-running POST and DELETE operations.
239239

240240
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.
492492

493493
Note that when `top` specifies a value larger than the server-driven paging page size, the response will be paged accordingly.
494494

495+
## Conditional Requests
496+
497+
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.
498+
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.
499+
Both of these scenarios can be accomplished with conditional requests, where the client specifies a _precondition_
500+
for execution of a request, based on its last modification date or entity tag ("ETag").
501+
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.
502+
503+
### Cache Control
504+
505+
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).
506+
A client can make a "conditional GET request" for the resource, with a precondition header that requests that
507+
data be returned only when the version on the service does not match the Etag or last modified date in the header.
508+
If there are no changes, then there is no need to return the resource, as the client already has the most recent version.
509+
510+
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`.
511+
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.
512+
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.
513+
514+
### Optimistic Concurrency
515+
516+
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.
517+
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.
518+
For example, the client can specify an `If-Match` header with the last ETag value received by the client in an update request.
519+
The service processes the update only if the Etag value in the header matches the ETag of the current resource on the server.
520+
By computing and returning ETags for your resources, you enable clients to avoid using a "pessimistic" strategy where the "last write always wins."
521+
495522
## Getting Help: The Azure REST API Stewardship Board
496523
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.
497524

azure/Guidelines.md

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ While removing a value from an enum is a breaking change, adding value to an enu
799799
<a href="#deprecation" name="deprecation"></a>
800800
### Deprecating Behavior Notification
801801

802-
When the [API Versioning](#API-Versioning) guidance above cannot be followed and the [Azure Breaking Change Reviewers](mailto:[email protected]) 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.
802+
When the [API Versioning](#api-versioning) guidance above cannot be followed and the [Azure Breaking Change Reviewers](mailto:[email protected]) 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.
803803

804804
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.
805805

@@ -1020,28 +1020,38 @@ Depending on the requirements of the service, there can be any number of "input"
10201020

10211021
<a href="#condreq" name="condreq"></a>
10221022
### Conditional Requests
1023-
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.
1024-
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.
1025-
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.
10261023

1027-
<a href="#condreq-return-etags" name="condreq-return-etags">:ballot_box_with_check:</a> **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).
1024+
The [HTTP Standard][] defines request headers that clients may use to specify a _precondition_
1025+
for execution of an operation. These headers allow clients to implement efficient caching mechanisms
1026+
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".
1027+
1028+
[HTTP Standard]: https://datatracker.ietf.org/doc/html/rfc9110
1029+
1030+
<!-- condreq-support-etags-consistently has been subsumed by condreq-support but we retain the anchor to avoid broken links -->
1031+
<a href="#condreq-support-etags-consistently" name="condreq-support-etags-consistently"></a>
1032+
<!-- condreq-for-read has been subsumed by condreq-support but we retain the anchor to avoid broken links -->
1033+
<a href="#condreq-for-read" name="condreq-for-read"></a>
1034+
<!-- condreq-no-pessimistic-update has been subsumed by condreq-support but we retain the anchor to avoid broken links -->
1035+
<a href="#condreq-no-pessimistic-update" name="condreq-no-pessimistic-update"></a>
1036+
<a href="#condreq-support" name="condreq-support">:white_check_mark:</a> **DO** honor any precondition headers received as part of a client request.
10281037

1029-
<a href="#condreq-support-etags-consistently" name="condreq-support-etags-consistently">:ballot_box_with_check:</a> **YOU SHOULD** use `ETag`s consistently across your API, i.e. if you use an `ETag`, accept it on all other operations.
1038+
The HTTP Standard does not allow precondition headers to be ignored, as it can be unsafe to do so.
10301039

1031-
You can learn more about conditional requests by reading [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232).
1040+
<a href="#condreq-unsupported-error" name="condreq-unsupported-error">:white_check_mark:</a> **DO** return the appropriate precondition failed error response if the service cannot verify the truth of the precondition.
10321041

1033-
#### Cache Control
1034-
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.
1042+
While conditional requests can be implemented using last modified dates, entity tags ("ETags") are strongly
1043+
preferred since last modified dates cannot distinguish updates made less than a second apart.
1044+
1045+
<a href="#condreq-return-etags" name="condreq-return-etags">:ballot_box_with_check:</a> **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).
10351046

1036-
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`.
1037-
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.
1038-
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.
1047+
#### Conditional Request behavior
10391048

1040-
<a href="#condreq-for-read" name="condreq-for-read">:ballot_box_with_check:</a> **YOU SHOULD** implement conditional read strategies
1049+
This section gives a summary of the processing to perform for conditional headers.
1050+
See the [Conditional Requests section of the HTTP Standard][] for details on how and when to evaluate these headers.
10411051

1042-
When supporting conditional read strategies:
1052+
[Conditional Requests section of the HTTP Standard]: https://datatracker.ietf.org/doc/html/rfc9110#name-conditional-requests
10431053

1044-
<a href="#condreq-for-read-behavior" name="condreq-for-read-behavior">:white_check_mark:</a> **DO** adhere to the following table for guidance:
1054+
<a href="#condreq-for-read-behavior" name="condreq-for-read-behavior">:white_check_mark:</a> **DO** adhere to the following table for processing a GET request with conditional headers:
10451055

10461056
| GET Request | Return code | Response |
10471057
|:------------|:------------|:--------------------------------------------|
@@ -1050,15 +1060,7 @@ When supporting conditional read strategies:
10501060

10511061
For more control over caching, please refer to the `cache-control` [HTTP header](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control).
10521062

1053-
#### Optimistic Concurrency
1054-
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.
1055-
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).
1056-
1057-
<a href="#condreq-no-pessimistic-update" name="condreq-no-pessimistic-update">:warning:</a> **YOU SHOULD NOT** implement pessimistic update strategies, e.g. last writer wins.
1058-
1059-
When supporting optimistic concurrency:
1060-
1061-
<a href="#condreq-behavior" name="condreq-behavior">:white_check_mark:</a> **DO** adhere to the following table for guidance:
1063+
<a href="#condreq-behavior" name="condreq-behavior">:white_check_mark:</a> **DO** adhere to the following table for processing a PUT, PATCH, or DELETE request with conditional headers:
10621064

10631065
| Operation | Header | Value | ETag check | Return code | Response |
10641066
|:------------|:--------------|:------|:-----------|:------------|----------------|
@@ -1069,7 +1071,8 @@ When supporting optimistic concurrency:
10691071
| DELETE | `If-Match` | value of ETag | value matches the latest value on the server | `204-No Content` | Response body SHOULD be empty. |
10701072
| DELETE | `If-Match` | value of ETag | value does NOT match the latest value on the server | `412-Preconditioned Failed` | Response body SHOULD be empty.|
10711073

1072-
#### Computing ETags
1074+
#### ETags
1075+
10731076
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.
10741077

10751078
<a href="#condreq-etag-is-hash" name="condreq-etag-is-hash">:ballot_box_with_check:</a> **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
10841087

10851088
<a href="#condreq-weak-etags-allowed" name="condreq-weak-etags-allowed">:heavy_check_mark:</a> **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.
10861089

1087-
<a href="#condreq-etag-depends-on-encoding" name="condreq-etag-depends-on-encoding">:white_check_box:</a> **DO**, when supporting multiple representations (e.g. Content-Encodings) for the same resource, generate different ETag values for the different representations.
1090+
<a href="#condreq-etag-depends-on-encoding" name="condreq-etag-depends-on-encoding">:white_check_mark:</a> **DO**, when supporting multiple representations (e.g. Content-Encodings) for the same resource, generate different ETag values for the different representations.
10881091

10891092
<a href="#telemetry" name="telemetry"></a>
10901093
### Distributed Tracing & Telemetry

0 commit comments

Comments
 (0)