In distributed systems, the reason idempotency is such a Big Deal™ is that it can be combined with at-least-once delivery to achieve exactly-once semantics. This isn't that. What you're describing as idempotency has the potential to mislead users, especially since you use examples like credit card processing.
You might be on to something here:
> Maybe describe it as a retry policy, or as a choice between at-least-once and at-most-once
and we will consider changing it because I agree that conflating the concepts (or the appearance thereof) is not worth diluting the other parts of the offering.
I still struggle to find the difference between this approach and the likes of Stripe [1] and Temporal [2] because in practice, it yields the same result.
[1] https://docs.stripe.com/api/idempotent_requests
[2] https://www.restack.io/docs/temporal-knowledge-temporal-io-i...
I find both those comparisons are apples to oranges:
1. Temporal does not claim, on that page, to be able to automatically make your calls idempotent. Instead, it is explaining how you - as the user - can and should write your activities to be idempotent. Take the payment code snippet: you can't just wrap `ExternalPaymentAPI.Process(paymentId, amount)` in an idempotency provider. You have to implement specific idempotency logic inside the activity. This is in line with the distributed systems theory of idempotency.
2. In these docs, Stripe describes an interface for achieving idempotency as an external caller. Since Stripe control the implementation of their own system, it is certainly possible that they have implemented their operations in a way that is truly idempotent. What your docs describe - the ability to make arbitrary operations idempotent by wrapping them - is simply not possible.
I wonder whether you're focusing on the idempotency key as the thing that is wrong here. There is nothing wrong with idempotency keys themselves - they are a great way for APIs to provide idempotency. The problem is that this guarantee can't be provided by a wrapper layer, it requires the operation itself to be written in an idempotent way. This is an example of the age old "end-to-end principle" from networks class.
I now realise how this statement could be misleading.
> To mark a function as idempotent, simply wrap it with the idempotent function.
When initially writing it, I thought it was a "given" that the wrapped functions are the responsibility of the developer. Now I realise this can also be interpreted as "we make your functions idempotent".
Thanks for the detailed breakdown. We will work on the docs to make it clearer.
The scenario where you'd want to use this `tryOnce` policy is probably the following:
1. You have an operation that is not idempotent and can't be made idempotent, for whatever reason.
2. You would prefer the failure mode of that operation to be that the effect doesn't take place, rather than that the effect takes place more than once.
BTW, you don't need to have an answer for everything. I don't think there has been any miscommunication or misunderstanding of the docs. I think people in this thread, including myself, have accurately identified that you haven't fully thought some of this stuff through. That's fine and normal for a product in this stage, but not being willing to admit it is less of a good sign. Good luck with the startup.
No, this is not at all what I meant. I don't think there's any value in introducing at-most once semantics to an already idempotent function.
I'm definitely not trying to have an answer for everything. But I guess we can leave it at that, at this point.