Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async Polling Standards #70

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions standards/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Asynchronous

## Overview

There are number of situations where REST API endpoints require longer processing time to handle specific requests. Those type of particular operations have to run in the background outside of the request processing scope. These API endpoints should be designed to handle asynchronous communication in order to make the results of the request available to the consumer.

There are couple ways to accomplish asynchronous communication in REST APIs and those are not mutually exclusive, including polling, websockets and webhooks.

```note
At this time only asynchronous communication through polling are provided in the API Standards.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scoping this to just polling right now.

```

## Polling (Resource without Status Monitor)

Overall guidelines for asynchronous requests are:
- API requests **SHOULD** be implemented as asynchronous if the 99th percentile response time is greater than 5s.
- API requests **SHOULD NOT** decide or interpret whether a response will be asynchronous or synchronous dynamically at runtime. An endpoint should be either synchronous or asynchronous, but not both.
- PATCH requests **MUST NOT** be used as asynchronous request initiation. If entity update is required and takes more than established time then it **MUST** be implemented as a POST request.

All asynchronous endpoint requests start with initiation. Since long-running requests need to be processed on background, asynchronous requests should initiate those processes with necessary input parameters. Background process details should be returned.

- Asynchronous requests **MUST** be a `POST` method, and should be represented as a resource, not an action-style endpoint.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will we allow async on DELETE? if so, it doesn't work with the pattern where status monitor and resource are the same object.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why DELETE conflict with status monitor? I think background job can be cancelled, but not deleted.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current pattern the status monitor and the resource are the same. So you'd have to make a GET request to the resource with a given id... and poll until you get a 404 on it?

- Asynchronous requests **SHOULD** be have an associated `GET` method for the same resource to retrieve the status of the request.
- Asynchronous requests **MUST** perform as much validation as possible before initiating the long-running process, and return 400 (Bad Request) [error response](errors.md) if validation fails.
- Asynchronous requests **MUST** return error details as part of completed long-running process response (other than request validation failures).
- Asynchronous requests **MUST** return 202 (Accepted) response status code when a long-running processing requests was successfully initiated.
- No other 2xx codes **SHOULD** be returned in response to the asynchronous request initialization, even if request has completed before the initiating request returns.
- `Operation-Id` header is **MAY** be supported and can be provided as part of the request.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What type is Operation-Id.. I assume it's an opaque string, but double checking.

- Process ID **SHOULD** be automatically generated and returned to the client if `Operation-Id` header is not provided.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth calling out that newly generated process id will be returned in the same Operation-Id header it it wasn't specified by client in request

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I struggled on the reason why we would want the Client/Consumer to ever specify the operation-id... and why not we always create and return one from the API?

- Asynchronous request **MUST** have a response body that extends from the following schema:
```json
{
"id": "string", // required: unique id representing the resource
"ref": "sps-ref", // optional
"status": "SUCCEEDED" | "FAILED" | "RUNNING" | "NOT_STARTED" | "CANCELED", // required
"detail": "same as detail from bulk... maybe error message? or complex object?",// required
"result": "", // optional
"completenessDescription": "percentage?", // optional
"createdDateTime": "", // required
"createdBy": "" | null, // optional
"completedDateTime": "" | null, // required
...EXTENDED...
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be the response body of the status monitor or the object to be extended where status monitor and resource are combined.

```

Example workflow for an asynchronous resource creation request:

```
// REQUEST
POST /reports // initiates long running processing by posting job with new parameters
Operation-Id: f7cf8412-08ed-40c9-ac1b-296da9d1d970
{
...Custom Request Body Parameters
}

// RESPONSE
202 ACCEPTED
Operation-Location: /reports/f7cf8412-08ed-40c9-ac1b-296da9d1d970
{
"id": "f7cf8412-08ed-40c9-ac1b-296da9d1d970",
"result": null
}
```

Once new long-running request posted, corresponding instance of running process created for tracking purpose. To access details about process and completeness status GET operation **MUST** be used. Such request will return current status of the background process along with the request parameters.

- Status of long-running processing **MUST** be always returned via GET request with provided `id` of the asynchronous process

```
// request to get details about already posted asynchronous request
GET /reports/f7cf8412-08ed-40c9-ac1b-296da9d1d970

// RESPONSE
200 OK
{
"id": "f7cf8412-08ed-40c9-ac1b-296da9d1d970",
"ref": "sps:report::f7cf8412-08ed-40c9-ac1b-296da9d1d970",
"status": "RUNNING",
"detail": "same as detail from bulk... maybe error message? or complex object?",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same questions on if details on errors should be some standard error format? definitely this answer should match between this and bulk updates.

"result": null,
"completenessDescription": "1 of 7",
"createdDateTime": "2023-07-21T17:32:28.000Z",
"createdBy": "user-xxxxx",
"completedDateTime": "2023-07-21T17:32:28.000Z"
}
```

## Polling (Endpoint with Status Monitor)

Do we need to support this? https://github.com/microsoft/api-guidelines/blob/vNext/azure/ConsiderationsForServiceDesign.md#long-running-operations
Loading