.NET IdempotentAPI 1.0.0 Release Candidate
A distributed system consists of multiple components located on different networked computers, which communicate and coordinate their actions by passing messages to one another from any system. Fault-tolerant applications can continue operating despite the system, hardware, and network faults of one or more components.
Idempotency in Web APIs ensures that the API works correctly (as designed) even when consumers (clients) send the same request multiple times. To simplify the integration of Idempotency in an API project, we could use the
IdempotentAPI open-source NuGet library. IdempotentAPI implements an ASP.NET Core attribute (filter) to handle the HTTP write operations (POST and PATCH) to affect only once for the given request data and idempotency key.
In July 2021, we saw how the
IdempotentAPI v0.1.0-beta in .NET Nakama (2021, July 4) provides an easy way to develop idempotent Web APIs in .NET Core. Since then, with the community’s help, several issues and improvements have been identified and implemented. The complete journey of the
IdempotentAPI is available in the CHANGELOG.md file.
IdempotentAPI 1.0.0-RC-01 is available with many improvements 🎉✨. In the following sections, we will see the complete features, details regarding the improvements, the available NuGet packages, and instructions to start using the IdempotentAPI library quickly.
- ⭐ Simple: Support idempotency in your APIs easily with three simple steps 1️⃣2️⃣3️⃣.
- 🔍 Validations: Performs validation of the request’s hash-key to ensure that the cached response is returned for the same combination of Idempotency Key and Request to prevent accidental misuse.
- 🌍 Use it anywhere!:
IdempotentAPItargets .NET Standard 2.0. So, we can use it in any compatible .NET implementation (.NET Framework, .NET Core, etc.). Click here to see the minimum .NET implementation versions that support each .NET Standard version.
- ⚙ Configurable: Customize the idempotency in your needs.
- Configuration Options (see the GitHub repository for more details)
- Logging Level configuration.
- 🔧 Caching Implementation based on your needs.
DistributedCache: A build-in caching based on the standard
- 🦥 FusionCache: A high-performance and robust cache with an optional distributed 2nd layer and advanced features.
- … or you could use your implementation 😉
IDistributedCache interface doesn’t support a command to
GetOrSet a cached value with atomicity. However, it defines the
Set methods. In our previous implementation, we used these two methods without locking (i.e. without grouping into a single logical operation). As a result, we had an issue with concurrent requests with the same idempotency key. The problem was that the controller action could be executed multiple times.
As we can observe in Figure 1, this issue happens when the API Server receives a second request (with the same idempotency key) before we flag the first idempotency key as Inflight (i.e., execution in progress). Thus, racing conditions occur when setting idempotency key as Inflight.
To overcome this issue, we defined the
IIdempotencyCache interface and implemented the
GetOrSet method, which performs a lock (locally) for each idempotency key (see code below). In Figure 2, we can see how we used the
GetOrSet method to execute the controller action only once on concurrent requests with the same idempotency key.
The idea is to use
GetOrSet method to set an Inflight object with a dynamic unique id per request when the
Get method returns Null (it doesn’t have a value) as a single logical operation. The second call of the
GetOrSet will wait for the first call to complete. Thus, only the execution that receives its unique id can continue with the execution of the controller action.
To overcome the concurrent requests with the same idempotency key issue, we defined the
IIdempotencyCache interface and implemented the
GetOrSet method. This is implemented in our build-in
DistributedCache caching project, which is based on the standard
Our implementation provides basic caching functionality. However, by defining the
IIdempotencyCache interface, our IdempotencyAPI logic becomes independent from the caching implementation. Thus, we can support other caching implementations with advanced features, such as the FusionCache.
FusionCache is a high-performance and robust caching .NET library with an optional distributed 2nd layer with advanced features, such as fail-safe mechanism, cache stampede prevention, fine-grained soft/hard timeouts with background factory completion, extensive customizable logging, and more.
BinaryFormatter serialization methods become obsolete from ASP .NET Core 5.0. In the
IdempotentAPI project, the
BinaryFormatter was used in the
Utils.cs class for serialization and deserialization. As a result, our library was not working in .NET Core 5.0 and later versions unless we enabled the
BinaryFormatterSerialization option in the
The recommended action based on the .NET documentation is to stop using
BinaryFormatter and use a JSON or XML serializer. In our case, we used the Newtonsoft JsonSerializer, which can include type information when serializing JSON, and read this type of information when deserializing JSON to create the target object with the original types.
In the following JSON example, we can see how the type of information is included in the data.
Storing-caching data is necessary for idempotency. Therefore, the IdempotentAPI library needs an implementation of the
IIdempotencyCache to be registered in the
Startup.cs file depending on the used style (.NET 6.0 or older). The
IIdempotencyCache defines the caching storage service for the idempotency needs.
Currently, we support the following two implementations (see the following table). However, you can use your implementation 😉. Both implementations support the
IDistributedCache either as primary caching storage (requiring registration) or secondary (optional registration).
Thus, we can define our caching storage service in the
IDistributedCache, such as in Memory, SQL Server, Redis, NCache, etc. See the Distributed caching in the ASP.NET Core article for more details about the available framework-provided implementations.
|Support Concurrent Requests||Primary Cache||2nd-Level Cache||Advanced Features|
Install the IdempotentAPI.Cache.DistributedCache via the NuGet UI or the NuGet package manager console.
Install the IdempotentAPI.Cache.FusionCache via the NuGet UI or the NuGet package manager console. To use the advanced FusionCache features (2nd-level cache, Fail-Safe, Soft/Hard timeouts, etc.), configure the
FusionCacheEntryOptions based on your needs (for more details, visit the FusionCache repository).
IDistributedCacheand register the FusionCache Serialization (NewtonsoftJson or SystemTextJson). For example, check the following code:
The response Data Transfer Objects (DTOs) need to be serialized before caching. For that reason, we will have to decorate the relative DTOs as
[Serializable]. For example, see the code below.
In your Controller class, add the following using statement. Then choose which operations should be Idempotent by setting the
[Idempotent()] attribute, either on the controller’s class or each action separately. The following two sections describe these two cases. First, however, we should define the Consumes and Produces attributes on the controller in both cases.
By using the Idempotent attribute on the API controller’s class, all POST and PATCH actions will work as idempotent operations (requiring the IdempotencyKey header).
By using the Idempotent attribute on each action (HTTP POST or PATCH), we can choose which of them should be Idempotent. In addition, we could use the Idempotent attribute to set different options per action.
|IdempotentAPI||The implementation of the
|IdempotentAPI.Cache||Defines the caching abstraction (
|IdempotentAPI.Cache.DistributedCache||The default caching implementation, based on the standard
|IdempotentAPI.Cache.FusionCache||Supports caching via the FusionCache third-party library.|
IdempotentAPI 1.0.0-RC-01 is available with many improvements 🎉✨. With the community’s help, several issues and improvements have been identified and implemented. I want to take this opportunity to thank @apchenjun, @fjsosa, @lvzhuye, @RichardGreen-IS2, and @william-keller for your support, ideas, and time to improve this library.