Hi Friends,
Welcome to the 107th issue of the Polymathic Engineer newsletter.
When making an API, it is easy to focus on the “happy path” of our applications. But if you want to build a robust application that doesn’t frustrate users, you also need to think about the “unhappy paths”.
Usually, the client handles errors by doing retries, either automatically or because users repeatedly hit the submit button. However, if the user sends multiple requests and more than one of them gets through, you may end up with duplicates of things on the server side.
This week, we will talk about how to make our applications more safe and robust by implementing idempotent APIs. The outline will be as follows:
Idempotency in APIs
Idempotent Keys
Retrying Requests
Answering to the clients
Project-based learning is the best way to develop technical skills. CodeCrafters is an excellent platform for practicing exciting projects, such as building your version of Redis, HTTP server, or Git from scratch. The challenges of building your own Kafka and interpreter are free during the beta period.
Sign up, and become a better software engineer.
Idempotency in APIs
Any software engineer who has worked with distributed systems and web services knows networks are inherently unreliable.
When a lot of data is sent over them, networks can fail weirdly. Even if routing issues, outages, and other problems don't happen often, they will still happen regularly.
To overcome this kind of environment, it's important to design APIs and clients that will be robust in the event of failure.
When an API request times out, the client has no idea whether the server received the request or not. For example, the server could have processed the request and crashed right before sending a response back to the client. Or the server could have processed the request, but the connection failed before returning a response to the client.
An effective way for clients to deal with transient failures is to retry the request one or more times until they get a response. However, this approach may cause problems if the request is not idempotent.
In the context of APIs, an idempotent endpoint ensures that making the same request more than once has the same effect as making it only once. According to the HTTP semantics, some request methods are considered inherently idempotent as the effect of executing multiple identical requests doesn't change the result.
GET and DELETE are idempotent because retrieving and deleting a resource multiple times should not change the server's state. PUT should also be considered idempotent in modern REST since such a request means a resource should be created or replaced entirely with the contents of the request's payload.
However, not all requests are idempotent by nature. Consider a POST request to add a new product to a catalog. If the server successfully processes the request but the client doesn't respond, retrying the request will create two products, which is not what the client intended.
The client might deal with this by implementing some logic that checks for duplicate products and removes them when the request is retried. However, this method would introduce a lot of complexity for the client.
A better approach would be for the server to make the POST request idempotent so that no matter how many times that specific request is retried, it will appear as if it only executed once.
A similar reasoning applies also to PATCH requests. If they are used to simply update the email address of a user without having to send other details through, the requests could be considered as idempotent. However, if PATCH is used to do other operations such as copy, move, add and remove, it can’t be considered as idempotent.
In the following section, we will see how it is possible to make idempotent requests like POST or PATCH.
Idempotent Keys
The underlying idea of idempotent APIs is to have a mechanism to identify and track requests uniquely. This is where idempotency keys come into play.
An idempotency key is a unique identifier the client generates and sends with each request. This key is used as a fingerprint, allowing the server to detect and handle duplicate submissions of the request.
On the client side, the implementation is quite straightforward:
The client generates a unique idempotency key for each request. This could be a UUID (Universally Unique Identifier) or any other string guaranteed to be unique for each operation.