Building Resilient Systems with Idempotent APIs
Networks are unreliable but our systems cannot be.
What is Idempotency?
Idempotency is a property of API design that ensures that making the same request multiple times produces the same result as making it once. In other words, no matter how many times an idempotent API endpoint is invoked with the same set of parameters, the outcome remains unchanged after the first successful request.
In the context of API designing, idempotency is crucial to prevent unintended side effects and ensure the predictability and reliability of the API. It allows clients to safely retry requests without causing any data duplication, overwriting, or other unwanted effects.
Idempotent API for file upload. Image by author
Idempotency of an API is determined by the changes it makes to the system, not the response it provides.
To better understand the above statement, consider this example:
- Consider an API endpoint that is designed to sign up a new user account in a web application. If this API is idempotent, it means that no matter how many times the API is called with the same input data (e.g., the same email and password), it will create the user account only once, and any subsequent invocations will have no further effect.
- The API may return a successful response (e.g., status code 200) for the first request and subsequent requests, indicating that the user account already exists, but the system state remains unchanged.
- The idempotency is evaluated based on the side effect of creating the user account, not the response message.
Real World Use Cases
Payment Processing: Idempotent APIs prevent double charging when processing payments, ensuring consistent and accurate billing.
Order Processing: Idempotent APIs in e-commerce platforms avoid duplicate orders or unintended changes to order status.
File Uploads: Idempotent APIs for file uploads prevent unnecessary duplication, ensuring files are stored only once, even during retries or network issues.
Subscription Management: Idempotent APIs handle subscription requests without creating duplicate subscriptions or unwanted changes to user preferences.
Distributed Systems: Idempotent APIs in distributed systems maintain consistency and handle failures gracefully, enabling safe retries without data inconsistencies.
How to Implement Idempotency in API Design?
Assign unique identifiers: Use UUIDs or other unique identifiers for each request to track and identify requests.
Idempotent HTTP methods: Design APIs using idempotent HTTP methods like GET, PUT, and DELETE. These methods ensure that multiple identical requests have the same effect as a single request.
Expiration time for idempotency keys: Set a reasonable expiration time for idempotency keys to ensure they are valid only for a certain period.
Response codes and headers: Utilize appropriate HTTP status codes (e.g., 200, 201, 204) and headers (e.g., ETag, Last-Modified) to indicate idempotency and successful processing.
HTTP Methods and Idempotency
Idempotent methods are those that can be safely repeated multiple times without changing the result beyond the initial operation.
The HTTP methods that are idempotent are GET, HEAD, PUT, and DELETE.
POST is not idempotent. This is because each time you make a POST request, it creates a new resource on the server, leading to a different result with each request. Subsequent POST requests will create additional resources, altering the state of the server, making it non-idempotent.
What are Idempotency Keys?
Before making an API call, the client requests a random ID from the server, which acts as the idempotency key.
The client includes this key in all future requests to the server. The server stores the key and request details in its database.
When the server receives a request, it checks if it has already processed the request using the idempotency key.
If it has, the server ignores the request. If not, it processes the request and removes the idempotency key, ensuring processing is done only once.
Example
In the example below, the server generates an idempotency key and returns it to the client in the response header (Idempotency-Key). The client must include this key in all subsequent requests. The server checks if it has already processed the request using the idempotency key and ensures exactly-once processing.
Note - This example is not intended for production use; instead, it serves to illustrate the fundamental concept of idempotent keys.
Node.js (Express)
const express = require('express');
const app = express();
const { v4: uuidv4 } = require('uuid');
const idempotencyKeys = new Set();
app.use(express.json());
app.post('/api/resource', (req, res) => {
const idempotencyKey = req.header('Idempotency-Key');
if (idempotencyKeys.has(idempotencyKey)) {
return res.status(200).json({ message: 'Request already processed' });
}
const resourceId = uuidv4();
// ... add logic to create the resource
idempotencyKeys.add(idempotencyKey);
return res.status(201).json({ resource_id: resourceId });
});
app.listen(3000, () => {
console.log('Server started on port 9000');
});
Python (Flask):
from flask import Flask, request, jsonify
import uuid
app = Flask(__name__)
idempotency_keys = set()
@app.route('/api/resource', methods=['POST'])
def create_resource():
idempotency_key = request.headers.get('Idempotency-Key')
if idempotency_key in idempotency_keys:
return jsonify({'message': 'Request already processed'}), 200
resource_id = str(uuid.uuid4())
# ... add logic to create the resource
idempotency_keys.add(idempotency_key)
return jsonify({'resource_id': resource_id}), 201
if __name__ == '__main__':
app.run()
Conclusion
Idempotent APIs play a crucial role in ensuring the reliability, consistency, and efficiency of a system.
If you like what you read, consider subscribing to my newsletter. Find me on GitHub, Twitter