Idempotency - Why Every API Should Be 'Stubborn'?
In a distributed world, 'retry' is a daily occurrence. How do you ensure a customer isn't charged twice when the network is unstable?
Idempotency might sound like a distant mathematical term, but in Microservices, it is the only "insurance" that keeps you from burning through your customers' accounts.
Context: When the Network is Unreliable
In a Monolith model, calling a function is instantaneous. In Microservices, calling an API is an adventure through network cables. What happens if:
- Service A calls Service B to pay $100.
- Service B processes successfully, deducts the money.
- But... the network is cut right as B is about to return the result to A.
- Service A sees a timeout, thinks to itself: "Maybe it didn't receive it", and automatically calls again.
If Service B isn't "smart," the customer just lost $200 for one item. Welcome to disaster!
1. Solution: Idempotency Key
Every API that performs an action that changes data (POST/PUT/DELETE) must accept an Idempotency Key from the Client (here, usually the calling Service or BFF).
Workflow at the receiving Service (B):
- Receive request with
Idempotency-Key: xxx-123. - Check in Redis/Database: Has this key been processed?
- If already processed: Return the previous result immediately (don't re-run business logic).
- If not yet processed: Run logic, save the result along with the key to the DB, and then return.
2. Implementation for Small Teams
Don't try to make the key management system too complex.
- Use Redis: With a TTL (Time To Live) of about 24h for keys. Enough to handle typical retry cases.
- Leverage the Database itself: A unique constraint on a
request_idcolumn in aTransactionstable is also an extremely robust and simple way to implement idempotency.
3. Trade-off: Storage and Latency
- Storage: You have to spend extra space to store these keys and their corresponding results.
- Latency: Adds a step to check the DB/Redis before every action.
But trust me: This price is too cheap compared to spending a whole week on manual reconciliation and apologizing to customers for incorrect charges.
Lessons Learned
- Design Persistent Clients: Clients must know how to retry when encountering network errors, but should always send the same Key.
- Idempotency is Not Just for APIs: It also applies to Message Queue Consumers. If a message is sent twice into a queue (which is quite normal in NATS/RabbitMQ), your service must be sharp enough not to process it a second time.
- Learn from Stripe: Stripe's API is the gold standard for Idempotency design. If you have time, read their docs.
Conclusion
In distributed systems, errors are certain to happen. Instead of trying to prevent network errors (which is impossible), design your services to be "stubborn" enough to handle repeated requests safely. That is the key to stability.
Series • Part 10 of 20