.NET IdempotentAPI v2.0.0RC Supports Idempotency in a Cluster Environment
Introduction
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.
Now, 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 🎉✨. 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.
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 😉.
- 🏠
- ✳ NEW ✳- 🔀 Support idempotency in a Cluster Environment (i.e., a group of multiple server instances) using Distributed Locks.
- samcook/RedLock.net: Supports the Redis Redlock algorithm.
- madelson/DistributedLock: Supports multiple technologies such as Redis, SqlServer, Postgres and many more.
- … or you could use your implementation 💪.
Improvement Details
Caching as Implementation Detail V2
In the 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:
IIdempotencyAccessCache
in theIdempotentAPI.AccessCache
project: Provide theIdempotentAPI
a 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.IIdempotencyCache
in theIdempotentAPI.Cache.Abstractions
project: Defines the caching methods required from theIdempotentAPI
. So, we can provide multiple implementations for caching.IDistributedAccessLockProvider
in theIdempotentAPI.DistributedAccessLock.Abstractions
project: Defines the methods to support distributed access locking. So, we can provide multiple implementations for distributed locking.
Cache Only Success Responses
The CacheOnlySuccessResponses
attribute option is included (default value: true
) to cache only 2xx HTTP responses.
Idempotency in a Cluster Environment
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.
Update from v1.0.0 to v2.0.0
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 v2.0.0
.
- ❗ IMPORTANT: We should register the IdempotentAPI Core services (
services.AddIdempotentAPI()
), which were unnecessary inv1.0.0
. - ❗ IMPORTANT: The IdempotentAPI.Cache has been renamed to IdempotentAPI.Cache.Abstractions. So, you should remove the
IdempotentAPI.Cache
NuGet package and use theIdempotentAPI.Cache.Abstractions
when needed. - ❗ The
CacheOnlySuccessResponses
attribute option is included with a default value oftrue
to cache only 2xx HTTP responses. This behavior is changed fromv1.0.0
, which cache all responses. - ❗ The use of Distributed Locks is Optional. So, use it
only
when you want to support idempotency in a cluster environment.
Quick Start
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:
Step 1.A: 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 (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 | |
---|---|---|---|---|
IdempotentAPI.Cache.DistributedCache (Default) | ✔ | IDistributedCache | ❌ | ❌ |
IdempotentAPI.Cache.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).
💡 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:
Step 1.B (Optional): Register the Distributed Locks for Cluster Environment
Currently, we support the following two Distributed Lock implementations. However, you can use your implementation 😉.
Choice 1: None
If you do not need to support idempotency in a Cluster Environment, you do not have to register anything. So, skip this step 😉.
Choice 2: samcook/RedLock.net (Redis)
Install the IdempotentAPI.DistributedAccessLock.RedLockNet via the NuGet UI or the NuGet package manager console. The samcook/RedLock.net supports the Redis Redlock algorithm.
Choice 3: madelson/DistributedLock (Multiple Technologies)
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.
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.AccessCache | The access cache implementation of the IdempotentAPI project. |
IdempotentAPI.Cache (Obsolete) renamed to IdempotentAPI.Cache.Abstractions | 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. |
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 . |
IdempotentAPI.DistributedAccessLock.MadelsonDistributedLock | The Madelson DistributedLock implementation for the definition of the IdempotentAPI.DistributedAccessLock . |
Summary
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 IdempotentAPI
project.
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 😁.