To implement exactly-once delivery you first need exactly once delivery.
But this project doesn't implement exactly once delivery.
> It can be triggered by calling the http://localhost:8080/count/{counterUUID} endpoint.
Oh, but what if my network goes down right after I sent that request? Did it succeed? There is no way of knowing. So now I have to pick between sending again (at least once) or not (at most once).
You can get exactly once delivery. But you need to involve request IDs which you haven't done here. Imagine something like this:
PUT http://localhost:8080/count/{counterUUID}?request-id={requestUUID}.
The requestUUID must be unique per counter. It should be generated when the action occurred (like when the user clicks a button) and should be reused for retries. (Of course this will still be foiled by a user clicking a button multiple times)Now the server does:
INSERT INTO increments(counter_id, request_id, counted)
VALUES (:counterUUID, :requestUUID, FALSE)
ON CONFLICT DO NOTHING
Now instead of deleting the record you just mark `counted = TRUE`.In theory you can never delete records from this table. In practice you can add a timeout after which double-counting is acceptable.
If you really need exactly-once delivery you need to make it end-to-end. If you don't support end-to-end exactly-once than you are really just an at-least-once delivery system.
So you are making a guarantee that provides little if any benefits because it can not be tied into the whole pipeline.
In my experience, it's easier to reason about and build systems when idempotency is an application level concern. For example take a bank that has some messaging system to update account balances. While a exactly once system, if designed perfectly, might achieve this, you could also achieve this by building an idempotent "update balance" system. With application level idempotency, you have more flexibility to later add different paths or technologies without as many re-write headaches.
Also -- the messages per day stat seems irrelevant. I've yet to encounter many real world systems that don't have irregular bursty patterns. With this slow processing rate, you could basically have a single large burst and then normal traffic, but be unable to return to realtime latency for hours/days.
This is exactly what we are doing in my company :) But it’s always good to have an option and know that we can do it in specific cases without external calls. In this scenario it can be nice simplification.
If anyone is interested in a failed attempt to implement this you can see my write up [1]. It contains some learnings from the experimental implementation I created for Lightbus.
The implementation was based on this presentation [2]
Changing the definition of exactly-once delivery won’t help your users.