.NET IdempotentAPI 1.0.0 Release Candidate
Introduction
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.
Now, the 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.
Features
- ⭐ 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!:
IdempotentAPI
targets .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 standardIDistributedCache
interface. - 🦥 FusionCache: A high-performance and robust cache with an optional distributed 2nd layer and advanced features.
- … or you could use your implementation 😉
- 🏠
Improvement Details
Improving Concurrent Requests Handling
The standard IDistributedCache
interface doesn’t support a command to GetOrSet
a cached value with atomicity. However, it defines the Get
and 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.
Caching as Implementation Detail
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 IDistributedCache
interface.
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 is Obsolete
The 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 .csproj
file.
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.
Quick Start
Step 1: Register the Caching Storage
Storing-caching data is necessary for idempotency. Therefore, the IdempotentAPI library needs an implementation of the IIdempotencyCache
to be registered in the Program.cs
or 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.
IdempotentAPI.Cache Implementation |
Support Concurrent Requests | Primary Cache | 2nd-Level Cache | Advanced Features |
---|---|---|---|---|
DistributedCache (Default) | ✔ | IDistributedCache | ❌ | ❌ |
FusionCache | ✔ | Memory Cache | ✔(IDistributedCache) | ✔ |
Choice 1 (Default): IdempotentAPI.Cache.DistributedCache
Install the IdempotentAPI.Cache.DistributedCache via the NuGet UI or the NuGet package manager console.
Choice 2: Registering: IdempotentAPI.Cache.FusionCache
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).
IDistributedCache
and register the FusionCache Serialization (NewtonsoftJson or SystemTextJson). For example, check the following code:
Step 2: Decorate Response Classes as Serializable
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.
Step 3: Set Controller Operations as Idempotent
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.
Using the Idempotent Attribute on a Controller’s Class
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).
Using the Idempotent Attribute on a Controller’s Action
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.
NuGet Packages
Package Name | Description |
---|---|
IdempotentAPI | The implementation of the IdempotentAPI library. |
IdempotentAPI.Cache | Defines the caching abstraction (IIdempotencyCache ) that IdempotentAPI is based. |
IdempotentAPI.Cache.DistributedCache | The default caching implementation, based on the standard IDistributedCache interface. |
IdempotentAPI.Cache.FusionCache | Supports caching via the FusionCache third-party library. |
Summary
The 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.
Any help in coding, suggestions, questions, giving a GitHub Star, etc., are welcome 😉. If you are using this library, don’t hesitate to contact me. I would be happy to know your use case 😁.
If you liked this article (or not), do not hesitate to leave comments, questions, suggestions, complaints, or just say Hi in the section below. Don't be a stranger 😉!
Dont't forget to follow my feed and be a .NET Nakama. Have a nice day 😁.