For example, take a look at stripe-go's implementation, which automatically tags a request with a key if the user didn't specify one:
https://github.com/stripe/stripe-go/blob/67034d2205c0240ade9...
This works for all mutating requests, and is useful because the built-in retry system will automatically reuse the same key that was generated. Users can get the benefits of idempotency without really having to understand very well what's going on under the hood.
I suppose you could still do that by munging each request body, but IMO it's a nice feature to make sure that requests are the same as what the user specified. Also note that in practice the implementations are probably not that wildly different under the hood — despite being in a header, Stripe's idempotency is still being handled by the same application stack which processes the payment (i.e. not a middle box or load balancer).
IMO the idempotency key should be required to be set and make the user aware that they need to handle retries properly.
In the situation you describe, I think it would make more sense to just retry the call a couple times from the same pod so you don't have the sizable overhead of discarding it and creating a new one for every failure, in which case the automatic keys would work fine. And if there's a really good reason they're not, setting the keys manually is very easy. At some point if you're far enough off the beaten path, you have to expect to read some docs.
I find it insightful to think about a transport layer that is much less reliable than the internet: traditional/snail mail. In a proper business communication, every invoice has an invoice number such that the recipient can know whether they processed that invoice previously or not.
1. request (also command/mutation) idempotency (using Idempotency-Key in HTTP, or clientMutationId in GraphQL/Relay)
2. entity idempotency (using invoice_id)
(1) is more generic one and can be applied to any mutation with a side-effect, for example sending an email. It can also work with server-side generated IDs. It can be implemented as a generic middleware via cache or DB with TTL. That's what Stripe is doing.
(2) is more application-specific, for mutations actually changing a state (usually a DB row/document, or an entity managed by another service). Here the entity id should have a uniqueness constraint in the DB, and you should use either a semantic key (i.e. customer name), or a client-side generated ID like for example UUID.
I use a combination of both, to reduce a load on the DB while still having the same guarantees as using DB only.
In trading fintech there is the FIX API [0] which has been around for a while. It has a "Client Order Id" field which gets mapped to an (Exchange) "Order Id" field.
I've also worked on the Twitter apps. The API spec seems fairly clean [1], but at least when I was there some time ago, there were edge cases that both the HTTP error and app error codes had to be combined to find the true error in order to take the correct action and show an appropriate error message to the user.
Idempotency is a very app-specific error condition. So I'm definitely in the group of developers that would not want to see nor code for app errors at a transport like HTTP level. I feel that the header idea and spec has good intentions, but does not take into consideration the developer experience as an API grows more complex. It also forces changes at the app messaging level if one wants to support different transport types (UDP unicast or multicast, gRPC [2], etc.).
[0] https://en.wikipedia.org/wiki/Financial_Information_eXchange [1] https://developer.twitter.com/en/support/twitter-api/error-t... [2] https://grpc.github.io/grpc/core/md_doc_statuscodes.html
Disclaimer: principal at AWS, but just general observations on this topic.
Not a Googler, this is what I understood from reading similar things over multiple posts. Feel free to correct me.
https://developers.googleblog.com/2015/02/introducing-grpc-n...
It does however make a difference if not the body, but the protocol-level request is supposed to be the idempotent one, but I don't know in what kind of a scenario that would apply.
Could you kindly share your view on the security implications of this approach - in particular with reference to the security context outlined in section 5 of the IETF draft:
> For idempotent request handling, the resources MAY make use of the value in the idempotency key to look up a cache or a persistent store for duplicate requests matching the key. If the resource does not validate the value of the idempotency key prior to performing such a lookup, it MAY lead to various forms of security attacks and compromise.
For that reason I do not support idempotent retries for public API calls.
For the authenitcated users, you cache/store the idempotency keys scoped by the currently authenticated user, i.e:
{user_id, idempotency_key}
You can add more parts to thie key for better security, i.e.: {user_id, api_call_name, idempotency_key}
or even {user_id, api_call_name, input_args_hash, idempotency_key}
etc.
I work in payment and idempotency is always a discussion point. This draft links to one of our docs (though the draft points to a 404 URL, the correct link being[0]).
As the draft calls out, Google Standard Payments uses the "requestId" field as our idempontency key, which is part of the request body, rather than in the HTTP header. We did this specifically to decouple the protocol from the behavior of the request. We could take the existing definitions and put that payload into any other protocol and it would work without having to use that protocol's sidechannel data (aka, the HTTP header).
Our design makes idempontency part of the application layer, rather than any middlebox or load balancer.
[0] https://developers.google.com/standard-payments/guides/conne...