Skip to content

Recipe: Caching

Goal

Add query caching with clear key conventions and safe invalidation.

Prereqs

  • Base Clean Architecture template running.
  • Decide on cache store (memory for development or IDistributedCache in production).

Steps

  1. Add the caching package.
dotnet add src/Application/Application.csproj package CleanArchitecture.Extensions.Caching
dotnet add src/Infrastructure/Infrastructure.csproj package CleanArchitecture.Extensions.Caching
  1. Register caching services and configure TTLs.
builder.Services.AddCleanArchitectureCaching(
    options => { options.DefaultNamespace = "MyApp"; },
    behaviorOptions =>
    {
        behaviorOptions.DefaultTtl = TimeSpan.FromMinutes(5);
        behaviorOptions.CacheNullValues = false;
    });
  1. Add the query caching pipeline behavior.
builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<Program>();
    cfg.AddCleanArchitectureCachingPipeline();
});
  1. Opt in queries to caching.
using CleanArchitecture.Extensions.Caching;
using CleanArchitecture.Extensions.Caching.Abstractions;

[CacheableQuery]
public record GetOrdersQuery : IRequest<OrdersVm>;

// or
public record GetUserQuery(int Id) : IRequest<UserDto>, ICacheableQuery;
  1. Add invalidation for write operations (commands or domain events).
await cache.RemoveAsync(cacheScope.Create("GetOrdersQuery", hash));

Verify

  • First call hits the data source; the second call hits cache.
  • Different request parameters produce different cache keys.

Pitfalls

  • Cache stampede: keep locking enabled and set jitter.
  • Tenant-aware caching: install CleanArchitecture.Extensions.Multitenancy.Caching and call AddCleanArchitectureMultitenancyCaching when multitenancy is enabled.
  • Caching error responses: use ResponseCachePredicate to skip them.