Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR adds opt-in support for idempotent requests in JsonApiDotNetCore, as defined by IETF.
In a microservices world, where at-least-once delivery is common practice, APIs must be able to handle duplicate requests gracefully. According to the HTTP standard, only POST and PATCH are non-idempotent by nature. Since in JSON:API there is no way to express "multiply the existing value of attribute X by 3", we only need to handle POST.
When a client includes the "Idempotency-Key" HTTP header in a POST Resource (or atomic:operations) request, the server stores the response before sending it back. A subsequent POST Resource request with the same key just returns the recorded response instead of executing the request. In such cases, it sends back the idempotency key too, to inform the client this was a replay.
The mechanism applies to both success and error responses. If the client wants to retry a failed request, it should resend with another key.
The tricky part is handling concurrent requests with the same key. The chosen approach here is to wrap the entire request in a transaction, so that concurrent requests block on trying to insert a record with the same primary key. This varies per database provider and isolation level, so needs more testing.
An even more scalable solution would be to use an expiring lease that is atomically obtained, but then we'd need to know the maximum duration that processing a request can take. This is problematic when non-idempotent downstream external network service calls are made from resource definitions.
References on idempotency:
References on capturing request/response bodies in ASP.NET: