.NET IdempotentAPI v2.0.0RC Supports Idempotency in a Cluster Environment
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 February 2022, IdempotentAPI 1.0.0-RC-01 was released with many improvements. The community’s contribution plays a crucial role in the evolution of any open source project. The community found issues and provided fixes and ideas for improvements. So, I would like to thank everyone who helped the
IdempotentAPI project to be evolved.
IdempotentAPI 2.0.0-RC-01 is available with bug fixes and with the support of idempotency in a Cluster Environment (i.e., a group of multiple server instances) using Distributed Locks 🎉✨. The complete journey of the
IdempotentAPI is available in the CHANGELOG.md file.
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 😉.
- ✳ NEW ✳- 🔀 Support idempotency in a Cluster Environment (i.e., a group of multiple server instances) using Distributed Locks.
IdempotentAPI v1.0.0, we have introduced a caching abstraction (
IIdempotencyCache) to support other caching implementations, such as FusionCache. However, our needs have been expanded to abstract access to the cache and support distributed locks. In the following list, we can see the main abstractions:
IdempotentAPI.AccessCacheproject: Provide the
IdempotentAPIa simple access caching method. So, it orchestrates how to access the cache. The default implementation (
IdempotencyAccessCache) performs in-process and distributed access lock per idempotency key.
IdempotentAPI.Cache.Abstractionsproject: Defines the caching methods required from the
IdempotentAPI. So, we can provide multiple implementations for caching.
IdempotentAPI.DistributedAccessLock.Abstractionsproject: Defines the methods to support distributed access locking. So, we can provide multiple implementations for distributed locking.
CacheOnlySuccessResponses attribute option is included (default value:
true) to cache only 2xx HTTP responses.
Refactoring has been performed to include additional abstractions and distinguish the Caching (
IIdempotencyCache), Distributed Locks (
IDistributedAccessLockProvider), and accessing of them (
IIdempotencyAccessCache) to support idempotency in a cluster environment.
The issue when using multiple server instances is that we could retrieve the exact request (with the same Idempotency Key) in numerous servers concurrently. Thus, the applications will try to access the caching storage using the
GetOrSet method, and all of them will set their value and continue with the controller action execution. The reason is that in the
IdempotentAPI v1.0.0, the
GetOrSet method has an in-process locking. So, it works as expected when using only one application instance (see more details here).
To overcome this issue in cluster environments, we perform a distributed lock per Idempotency Key (in the in-process lock). In this way, only one of the concurrent requests of the multiple server instances will execute the
GetOrSet method. Thus, only one controller action will be executed per idempotency key. The rest of the requests will result in a 409 HTTP Conflict response.
A distributed lock performs a request to another system. Thus, we should use it only when it’s necessary because it will include a delay in our responses! So, the question that arises is how long we will wait to acquire the lock. Of course, the answer is that it depends on our use case. For that reason, the
DistributedLockTimeoutMilli attribute option is included to configure the time the distributed lock will wait for the lock to be acquired (in milliseconds).
Currently, we support the following two implementations. The next section will show how we can register the distributed lock implementations.
- samcook/RedLock.net: Supports the Redis Redlock algorithm.
- madelson/DistributedLock: Supports multiple technologies such as Redis, SqlServer, Postgres and many more.
Before updating to
v2.0.0, you should read the change log carefully because there are some breaking changes. The following list shows the essential parts when updating to
- ❗ IMPORTANT: We should register the IdempotentAPI Core services (
services.AddIdempotentAPI()), which were unnecessary in
- ❗ IMPORTANT: The IdempotentAPI.Cache has been renamed to IdempotentAPI.Cache.Abstractions. So, you should remove the
IdempotentAPI.CacheNuGet package and use the
- ❗ The
CacheOnlySuccessResponsesattribute option is included with a default value of
trueto cache only 2xx HTTP responses. This behavior is changed from
v1.0.0, which cache all responses.
- ❗ The use of Distributed Locks is Optional. So, use it
onlywhen you want to support idempotency in a cluster environment.
Let’s see how we could use the NuGet packages in a Web API project. The
IdempotentAPI can be installed via the NuGet UI or the NuGet package manager console:
and, register the IdempotentAPI Core services:
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 (require its registration) or as 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).
💡 TIP: To use the 2nd-level cache, we should register an implementation for the
IDistributedCache and register the FusionCache Serialization (NewtonsoftJson or SystemTextJson). For example, check the following code:
Currently, we support the following two Distributed Lock implementations. However, you can use your implementation 😉.
If you do not need to support idempotency in a Cluster Environment, you do not have to register anything. So, skip this step 😉.
Install the IdempotentAPI.DistributedAccessLock.MadelsonDistributedLock via the NuGet UI or the NuGet package manager console. The madelson/DistributedLock supports multiple technologies such as Redis, SqlServer, Postgres and many more.
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.AccessCache||The access cache implementation of the IdempotentAPI project.|
|IdempotentAPI.Cache (Obsolete) renamed to IdempotentAPI.Cache.Abstractions||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.DistributedAccessLock.Abstractions||The distributed access lock definition of the IdempotentAPI project.|
|IdempotentAPI.DistributedAccessLock.RedLockNet||The RedLockNet implementation for the definition of the
|IdempotentAPI.DistributedAccessLock.MadelsonDistributedLock||The Madelson DistributedLock implementation for the definition of the
IdempotentAPI 2.0.0-RC-01 is available with bug fixes and with the support of idempotency in a Cluster Environment (i.e., a group of multiple server instances) using Distributed Locks 🎉✨. Several issues and improvements have been identified and implemented with the community’s contribution. So, I would like to thank @kvuong, @bernardiego, @MohamadTahir, and @Rast1234 for your support, ideas, source code, and time in improving the