Build Retry-able API using idempotency key


 If you have been working on API implementation for awhile and try to make your API resilient & bulletproof, the topic today might be something you will find interesting. Today, nobody can guarantee the microservices that you built will not run into trouble. When issue occurred, we often hope that the simplest way to solve the issue is to retry and invoke the API again. Retry can be an easy mechanism handled by your middleware/ API orchestration product. The challenge for doing so will always be on the business logic of the API if the nature of the record can gracefully managed the duplication through something like Primary Key. (e.g. create a car record with car registration number) You probably won't hit a problem when performing record update (if multiple updates is not a problem. e.g. update status of your car record from "available" to "sold"),  but it will be tricky for scenario of record creation.(e.g. create a sales record for a car)

Making microservices resilient can be a nightmare if the solution to this has to always be implemented in business logic. Every business logic will then need to be reviewed and ensure that the logic can gracefully manage duplication. What if I tell you there is a consistent approach to check duplication without touching the business logic? You don't even need to crack your head to analyse the primary key and manage the duplication when your API need to be retried by your API workflow.

I found the answer as I looking at the implementation of idempotency key for API and it is really simple and straightforward. Basically, idempotency key is an unique key to represent a specific transaction within a particular validity period. Instead of implementing a business logic to check data duplication, API consumer need to issue a unique key called idempotency key when making a request. You can ten check idempotency key to confirm if this is an identical transaction. (Please see the diagram below for illustration.)

Overall Architecture

When API consumer send a record creation (e.g. sales record for a car) to a microservice , a GUID will be set to Idempotency-Key in HTTP header. This will be a unique key to represent this specific request. When your microservice received the request, the microservice just need to maintain a list of used idempotency key (i.e. key list) and check the key in a new request against the key list and returned conflict status if the key is duplicate. The key list might slowly built up after awhile. The best way to manage the key list will be including a validity period or TTL (Time To Live). Hence, the list can be easily housekeep to avoid the duplication check getting slower due to too many record. (You probably won't need to check against key used months ago as unlikely you will do a retry of API request after few months)  This can be nicely managed by caching solution such as Redis with TTL enabled. Based on IETF documentation, you can use HTTP 409 - Conflict status code when such scenario happens. 

Hands on Implementation


A explanation will not be good enough without an example. To give a better illustration, I will create a simple microservice to implement Idempotency Key. I will create a microservice using threadfin-http; a simple web container built on Node.JS. I will store Idempotency Key List with TTL to manage the list.

Download the Libraries 
To download the threadfin-http library, you may use the following command to download the library
 
npm install threadfin-http

Besides, threadfin-http, you also need a Redis library to help you connect your Redis server. I am using ioredis in this example. You may use the following command to download the library
 
npm install ioredis

 

Sample Implementation

I am using the latest version of threadfin-http for this implementation; which allows me to create API handler for different HTTP methods. In this example, I will create an API for HTTP PUT method. To do this, you need to create a "put" folder in your API folder. Then create a handler.js in the put folder. In this example I am creating an API call process. Hence, I will name my handler process_handler.js (with handler POSTFIX set as _handler in config.json)

process_handler in api/put/ folder

Below is the sample code of my process_handler.js:

Sample Code of Process_Handler.js

Firstly, you need to extract the idempotency-key from request header (Please see below)

var idempotency_key = req.headers['idempotency-key']

After that, you can use redis.get method to check the idempotency key in Redis server. Redis server will return null value if the submitted idempotency key is not available. 

If idempotency key doesn't exist, it means this request has not been processed previously. It  will be safe for us to create the record accordingly. After the record successfully processed, We can then use redis.set method to create an entry in Redis server. I use "EX", 120 to set TTL to 120 seconds for this record. TTL will automatically be used by Redis to purge the record when the set TTL is lapsed. 

If result does exist (i.e. else condition in sample code) , We may then set HTTP status code to 409 and send conflict status back to API consumer.


Testing the implementation

That's all we have to implement checking of idempotency key. Now, we can proceed to test the implementation using any HTTP client tool (e.g. POSTMAN). 

First, I send a HTTP PUT request (http:/localhost:8888/process) by setting Idempotency-Key to "123-123-123".  You use received HTTP status 200 as the idempotency key has not been used before.

POSTMAN result of 1st request

When I check Redis with any Redis client (e.g. RedisInsight) , you should be able to see the record created in Redis with TTL set based on what you specified. 
 
Record in Redis Server

When you submit the request again with the same Idempotency-Key, HTTP status code 409 will return instead HTTP status code 200 which we previously received.


POSTMAN result of 2nd request

If you wait for the TTL to lapse and you submit again with the same Idempotency Key, the API request will return HTTP status code 200 again. 


Wrapping Up...

I hope today's sharing give you a brief idea of how to use idempotency key to address you business problem. Do drop me a comment if you have any question. Happy coding ! 

Comments

Popular posts from this blog

API Versioning with Node.JS

Microservices Observability with Jaeger