Two-level caching in .NET with FusionCache and Redis
This guide shows how to combine FusionCache's in-process L1 memory with a shared Redis L2 layer in a product catalog API. The author keeps Clean Architecture boundaries intact and focuses on correct cache behavior rather than raw speed numbers. It is a practical starting point for developers who run several instances and need to plan freshness and invalidation.
Key points
- FusionCache 2.9.0 layers an in-process L1 cache over a Redis L2 layer; the core package alone only gives you L1.
- TTL controls freshness while eviction is a separate event driven by Redis memory limits such as maxmemory.
- Reads follow cache aside: check L1, then L2, and only then run the factory query against the database.
- The cache wraps the Application read interface as a decorator, so Domain and Application never reference FusionCache types.
- The local demo uses redis:8.2-alpine with maxmemory 128mb and allkeys-lru; production needs private networking, ACLs and TLS.
This tutorial walks through combining FusionCache with Redis in a product catalog API built with Clean Architecture. The database stays the source of truth, while an in-process L1 cache and a shared Redis L2 layer speed up reads across several instances. The author puts correct behavior ahead of impressive benchmark numbers, stressing that TTL is a freshness policy, that memory eviction is a separate event, and that a database change does not refresh existing copies on its own.
The design splits the code into Domain, Application, Infrastructure and Api projects, so the Application layer never references FusionCache or Redis types. Caching wraps the product query reader as a decorator. The setup uses FusionCache 2.9.0 with its System.Text.Json serializer and Redis backplane, plus Microsoft's StackExchangeRedis provider at 10.0.9, targeting net10.0 with a redis:8.2-alpine container. The local Redis config caps memory at 128 MB with allkeys-lru and disables persistence.
Real-world use
- A product catalog API running on several instances that serves frequently read product details without hitting the database each time.
- An e-commerce backend that can tolerate briefly stale product data after price changes, as long as the freshness policy is explicit.
- A Clean Architecture .NET service that needs to add caching around existing query readers without changing the domain or application code.
Why it matters
Wrapping the cache as a decorator and defining TTL, eviction and invalidation rules up front keeps both performance and freshness predictable across instances. As the article stresses, cache behavior is a design contract, so it matters more than any single benchmark number.