An idempotent API operation is an operation that produces the same effect no matter how many times it is executed.
For example, an API operation that sets a user's name is a typically idempotent operation. Whether it occurs one time or a thousand times, the effect of the operation is that the user's name will be set to the target value.
Idempotent operations do not necessarily return the exact same response output every time they are invoked, they only need to have the same impact upon the system. Deleting a resource is an example of this distinction; the first time you invoke the delete, the object is deleted. The second time you attempt to delete it, there is no change in the state of the system, but the object remains deleted as requested.
Less information will need to be stored and managed by the application. Reducing server state is particularly relevant to removing information that isn't strictly business oriented, but is kept around only to aid the implementation of services or workflows.
Influenced by: Idempotency.
The ability to safely retry requests that may or may not have been processed. A typical temporary failure that would benefit from this capability is a glitch during a high-availability failover, a temporary networking problem, or a small load spike.
Influenced by: Idempotency.
Data inconsistency errors are when logical or business constraints are violated by the data already stored and accepted into the system. These types of errors tend to propagate into other aspects of the system where logic was not designed to handle the data in its inconsistent state.
Benefits: Prevent Customer Frustration.
Influenced by: Prevent Conflicting Edits.
Scalability is the ability for a system to handle increased amounts of traffic, customers, and data with linearly increasing costs.
Not all systems are scalable. Unscalable systems have bottlenecks or restrictions that need to be solved, or they exhibit exponential growth in cost in relation to a linear growth in customers. For example, a system is unscalable if adding 1,000 users would result in $10,000 in costs, but the next 1,000 users would result in $100,000 in costs.
Benefits: System Supports More Customers.
Influenced by: Reduced Server State.
The existing server resources used to run the system are utilized at a higher rate in order to accomplish more work without new resources. This is, of course, only possible if the servers are not already completely utilized to their maximum.
Benefits: Reduced Server Costs.
Influenced by: Improved Concurrency.
Systems can automatically recover from failure if they're capable of classifying the type of the failure, classifying the type of the request that failed, and determining that there is a path of action that will result in a better outcome than returning an error.
As an example, a load balancer like HAProxy is capable of redispatching requests if a backend server disconnects while processing a request.
Benefits: Prevent Data Loss.
Influenced by: Retry to Recover from Temporary Failure.
Less money is being made by the business.
Influenced by: Increased Costs.
Customers are not pissed off by the way your application works, or doesn't work. Customer frustration is a very common problem with applications, and it can often occur without anyone knowing about it until it reaches a breaking point where a customer cannot stand anymore.
Benefits: Prevent Customer Loss.
For an HTTP API, idempotency of some HTTP methods is a requirement of RFC2616 § 9.1.2.
The HTTP methods GET, HEAD, PUT, and DELETE are required by the specification to be idempotent. The methods OPTIONS and TRACE are expected to be idempotent by virtue of having no side-effects.
The method POST is commonly used for any operations that are not idempotent.
Idempotent operations can be safely retried after any type of failure that might be a temporary failure. If the operation has already been completed successfully but the response was lost, then the idempotency of the operation guarantees that invoking it a second time is safe. If it had not been completed, then it would be processed as expected.
By ensuring that it is safe to recover from errors by retrying requests, it becomes far easier to develop a reliable API client. The client can retry requests without user intervention, and without requiring the user to resync their state and start an operation from scratch.
Mobile clients are a notoriously difficult environment to build software in. Limited CPU, memory, and storage capabilities are just a small part of the challenge; the unreliable networking is the biggest difficulty. The easier it is to build a reliable API client, the easier it will be to build a high-quality mobile client.
There will be cases where a request failure occurs that a client cannot retry. If you have an outage that affects your system's upstream networking, then the client issuing requests may not be immediately capable of retrying those requests, and other intervening forces may prevent the client from ever retrying that request.
An automatic failure recovery system can prevent the end-user from losing data that they expected, desired, and required to be entered into your system.
Even though you're providing an API as a service to your customers, it's likely that you're going to have to develop clients using it to. In fact, you probably want to, just to "dogfood" your own work.
Designing your API such that it is easier to develop reliable clients is going to end up reducing your own development efforts in building that client software.
Often a system that's designed for to support idempotent API operations will also prevent conflicting modification operations as well. It is not a strict requirement though, but there is a tendency for one implementation to address both issues.
A common way to implement an idempotent edit operation would include identifying the state of the resource or object before its edited, such as with a version number or a timestamp. In an HTTP API, this can be accomplished with the If-Match header using a resource's ETag. This makes the operation both:
Conflicting edits being processed can result in data loss. Preventing conflicting edits will prevent this form of data loss.
An example of the data loss would be two administrators simultaneously editing a user profile. The first admin changes the user's e-mail address, while the second admin changes the user's password. Depending upon the API design, the lack of a mechanism to prevent conflicting edits will cause one of these changes to be disregarded when both updates are submitted to the system simultaneously. One will be applied, then the second will be applied overwriting the first.
Conflicting edits being processed can result in an inconsistent data state. Preventing this requires either preventing conflicting edits, or it would require every API operation to perform validation checks that might be outside the scope of the operation being processed.
Preventing conflicting edits will prevent some possible occurrences of data inconsistency.
An alternative to preventing conflicting edits would be to serialize requests, either on a per-server, per-tenant, or per-user basis depending upon what is appropriate for the application. If this type of serialization can be avoided because conflicting edits are prevented, then request concurrency levels can be higher.
Customers will get frustrated if the impact of their API operations are silently lost because they'll have difficulty figuring out if they're responsible for the errors, or your system is. Avoiding that scenario is going to reduce the likelihood of customer frustration.
The ability to serve more requests simultaneously will, all other things being equal, generally increase the number of requests that can be served in a given time period.
However, there is usually a tipping point at which higher concurrency may result in lower throughput if your system spends too much time managing concurrent requests, or the concurrency level exceeds the threshold of the system.
Improving the throughput of request processing in your system will reduce the amount of time clients are waiting for API operations to complete. This should make the responsiveness of your API better, which will allow client applications to accomplish more work and be more responsive.
Particularly for interactive clients, improving the performance of an API client will reduce the amount of time spent waiting for API requests to be processed, which will alleviate customer frustration. A typical application of this will be a mobile client; the faster it performs, the more confident users feel.
Frustrated customers are customers that you're more likely to lose, especially if the cost of migrating to an alternative is relatively low. Many customers will actually suffer a lot of frustration before taking on a migration project, especially in the B2B service space, but it's usually not a great idea to test this theory intentionally.
Designing an API's idempotent operations requires a design where API operations are fully autonomous. The content of the API request must be logically complete and independent from server-side state in order for requests to be reappliable. As a result, this style of API design tends to require less server-side state.
The reduction of server-side state is particularly true when compared to distributed transaction coordination, which is sometimes considered an alternative to an idempotent API design. Distributed transactions require very coordinated shared state between multiple application servers; all components of that state are completely absent in an idempotent API.
Shared server state is a common bottleneck in scaling applications. Instead of scaling one component, such as the system's application servers, there is now a requirement to scale two systems simultaneously, as you'll need to scale your shared state services at the same time.
By reducing the amount of shared server state, you eliminate a scaling hurdle entirely.
Shared server state is often stored in a separate system than your traditional data store because it requires different access patterns and a higher write workload. As a result, the use of a shared server state storage is an additional point-of-failure for the system. By reducing or removing shared server state, this point-of-failure is eliminated as well.
Most API operations are easy to design and develop in an idempotent manner. But, there are always exceptions, and some API operations will be more difficult to develop in this way, and therefore will result in a longer implementation time and a higher development cost.
A simple example of a more difficult idempotent operation is the idempotent creation of objects or resources, particularly those that do not have natural primary keys.
Although for many API operations idempotency is just a matter of design, for some it will increase the complexity of implementing the operation. The operation may be required to compare the expected state of objects or resources with the current state to verify that the operation has not already been processed, for example.
Maintenance expenses are usually unavoidable on any development project. Additional features, tweaks, and bug fixes are the obvious maintenance costs, but every line of code in an application is a maintenance cost waiting to happen.
Complex implementations, even if implemented flawlessly, are more difficult and expensive to maintain in the future. Development staff will have increased training and ramp-up time to work on complex systems, and higher levels of quality control will be required for maintenance work relative to a simpler system.
Complex implementations tend to be more difficult to accurately and completely test. There are additional costs involved in communicating the complexity between departments, and time spent developing, verifying, and implementing test plans.
Some developers using your API will have expectations about its behavior based upon the HTTP specification. When there's a mismatch between your API and their expectations, it can be a source of confusion and frustration.
Specification expectations can also be coded into tools, frameworks, proxies, and other intermediaries between developers and your API. As an example, it would be spec conforming for an API framework to reissue PUT or DELETE requests automatically if they fail, based upon the expectation that these methods will be idempotent as per the HTTP spec.