/FluentCaching

Declarative C# library for objects caching with fluent api configuration and without boilerplate code. The project is currently in development.

Primary LanguageC#MIT LicenseMIT

Fluent caching

CI NuGet

FluentCaching library provides an abstraction layer over caching implementation (memory, Redis, etc.) with really small overhead (check benchmarks). Instead of writing boilerplate code to support caching, just configure caching policy for object using fluent api and use cache object to manipulate caching abstraction.

The core library is written in plain C# with no external dependencies. The nearest plan is to prepare the first release version.

Code samples

Configure caching policy by entity and build cache object

var cache = new CacheBuilder()
.For<User>(u => u.UseAsKey("user").CombinedWith(u => u.Id) // alternatively UseAsKey(u => $"user{u.Id}")
                .And().SetExpirationTimeoutTo(2).Minutes.And(10).Seconds
                .With().SlidingExpiration())
.Build();

Add object to cache

var user = _userService.GetUserById(42);

await cache.CacheAsync(user);

Retrieve object from cache

var userId = 42;

await cache.RetrieveAsync<User>(userId);

Remove object from cache

var userId = 42;

await cache.RemoveAsync<User>(userId);

Multi property configuration is supported with the same set of features

var cache = new CacheBuilder()
.For<User>(u => u.UseAsKey(u => u.FirstName).CombinedWith(u => u.LastName) // alternatively UseAsKey(u => u.FirstName + u.LastName)
                .And().SetExpirationTimeoutTo(2).Minutes.And(10).Seconds
                .With().SlidingExpiration())
.Build();

var userKey = new {FirstName = "John", LastName = "Doe"}; // may be any class or struct with corresponding properties
await cache.RetrieveAsync<User>(userKey);

Different cache implementations for different entities are supported

var cache = new CacheBuilder()
.For<User>(u => u.UseAsKey(u => u.FirstName).CombinedWith(u => u.LastName)
                .And().SetExpirationTimeoutTo(2).Minutes.And(10).Seconds
                .With().AbsoluteExpiration().And().StoreInMemory())
.For<Order>(o => o.UseAsKey(o => o.Date).CombinedWith("order")
                .And().SetInfiniteExpirationTimeout()
                .And().StoreInDistributedCache())
.Build();

Getting started

Use .NET CLI to install the target packages (or search VLobyntsev.FluentCaching in IDE package manager)

dotnet add package VLobyntsev.FluentCaching.DistributedCache
dotnet add package VLobyntsev.FluentCaching.Memory
dotnet add package VLobyntsev.FluentCaching.DependencyInjection

Define the caching configuration in the startup file

builder.Services.AddFluentCaching(cacheBuilder => cacheBuilder
    .For<Cart>(_ => _.UseAsKey(c => $"card-{c.Id}").And().SetExpirationTimeoutTo(5).Minutes
        .With().SlidingExpiration().And().StoreInMemory())
    .For<UserCheckoutStatistics>(_ => _.UseClassNameAsKey().CombinedWith(s => s.UserId)
        .And().SetInfiniteExpirationTimeout().And().StoreInDistributedCache())); // Need to configure distributed cache as well

app.UseFluentCaching(); // Needed for distributed cache only

Inject ICache object and use it

app.MapGet("/{cardId:guid}/card-items", (Guid cardId, ICache cache) => cache.RetrieveAsync<Cart>(cardId));

app.MapGet("/{userId:guid}/checkout-statistics",
    (Guid userId, ICache cache) => cache.RetrieveAsync<UserCheckoutStatistics>(userId));

For more information check the samples.

Current benchmarks

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19045
Intel Core i5-9300H CPU 2.40GHz, 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=7.0.202
  [Host]     : .NET Core 6.0.9 (CoreCLR 6.0.922.41905, CoreFX 6.0.922.41905), X64 RyuJIT
  DefaultJob : .NET Core 6.0.9 (CoreCLR 6.0.922.41905, CoreFX 6.0.922.41905), X64 RyuJIT

Method ItemsCount Mean Error StdDev Median Allocated
CacheWithComplexKey 100 0.3616 ms 0.0455 ms 0.1340 ms 0.3616 ms 53.72 KB
RetrieveWithComplexKey 100 0.0924 ms 0.0018 ms 0.0027 ms 0.0919 ms 47.27 KB
CacheWithSimpleKey 100 0.0821 ms 0.0051 ms 0.0148 ms 0.0736 ms 48.13 KB
RetrieveWithSimpleKey 100 0.0656 ms 0.0013 ms 0.0015 ms 0.0652 ms 38.75 KB
CacheWithComplexKey 1000 1.0152 ms 0.0473 ms 0.1349 ms 0.9603 ms 530.86 KB
RetrieveWithComplexKey 1000 1.0323 ms 0.0519 ms 0.1522 ms 0.9536 ms 476.17 KB
CacheWithSimpleKey 1000 0.9037 ms 0.0436 ms 0.1251 ms 0.8571 ms 484.06 KB
RetrieveWithSimpleKey 1000 0.6686 ms 0.0088 ms 0.0078 ms 0.6677 ms 390.32 KB
CacheWithComplexKey 10000 14.1614 ms 0.4543 ms 1.2437 ms 13.6662 ms 5382.46 KB
RetrieveWithComplexKey 10000 10.6172 ms 0.5431 ms 1.5139 ms 9.8729 ms 4835.57 KB
CacheWithSimpleKey 10000 12.1970 ms 0.2404 ms 0.5713 ms 12.0789 ms 4845.48 KB
RetrieveWithSimpleKey 10000 6.8092 ms 0.1594 ms 0.4471 ms 6.6842 ms 3905.95 KB