Caching Architecture–Testability, Dependency Injection and Multiple Providers

Note: Link to Wiki is here

Note: This article assumes familiarity with caching in .Net and dependency injection.

Update: Fixed broken links and updated some text based on feedback.

One of the things I have always stated is that caching is important to every applications performance. In many of the applications I design and work on, I like to introduce a caching layer from the outset, and ensure it is part of the vertical slice that I typically provide as an application blueprint.

Obviously we want to ensure that all this caching magic does not introduce hard dependencies to the underlying caching mechanism. For example, the ASP.NET caching engine is fantastic at what it does, however if you are writing components that make use of it, then they become hard to test as you need to introduce lots of HttpContext references which are time consuming and hard to mock out for testing purposes.

In order to prevent this, I like to define an ICache interface that defines our contract for working with a cache.

Something like this:

  1: public interface ICache 
  2: { 
  3:    void Add<T>(string cacheKey, DateTime expiry, T dataToAdd) where T : class; 
  4:    T Get<T>(string cacheKey) where T : class; 
  5:    void Add(string cacheKey, DateTime expiry, object dataToAdd); 
  6:    object Get(string cacheKey); 
  7:    void InvalidateCacheItem(string cacheKey); 
  8: }

With that, I can write a simple pass through CacheAdapter that implements this interface, but passes all the calls through to the ASP.NET Cache. Any components can then work with the CacheAdapter rather than the ASP.NET cache directly. This means we can mock it out easily by mocking the interface. Testing then becomes much easier and our code is loosely coupled. The CacheAdapter may look something like this:

  1: public class WebCacheAdapter : ICache
  2: {
  3:    private System.Web.Caching.Cache _cache;
  4:    public WebCacheAdapter()
  5:    {
  6:       if (System.Web.HttpContext.Current != null)
  7:          _cache = System.Web.HttpContext.Current.Cache;
  8:       else
  9:          throw new ArgumentNullException("Not in a web context, unable to use the web cache.");
 10:    }
 11: 
 12: public void Add<T>(string cacheKey, DateTime expiry, T dataToAdd) where T : class
 13: {
 14:    if (dataToAdd != null)
 15:       _cache.Add(cacheKey, dataToAdd, null, expiry, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
 16: }
 17: 
 18: public void Add(string cacheKey, DateTime expiry, object dataToAdd)
 19: {
 20:    Add<object>(cacheKey, expiry, dataToAdd);
 21: }
 22: 
 23: // rest of code omitted for brevity

So that’s all well and good. Even though I am primarily a web guy, I often design and work with desktop applications which cannot use the ASP.NET cache. In addition, I may also want to use the awesome power of Windows AppFabric Cache for distributed caching. Furthermore, in a few scenarios it has been unclear whether distributed caching was an option due to infrastructure concerns. So I would have liked to use AppFabric caching, but may not be able to, and I would not have known this till later in the project. Finally, I do this kind of code a lot, and I did not want to write it for each scenario and have to specialise it for each project.

It would be nice if I could have all the caching options I may need, already abstracted out for me, easily selectable via configuration, and utilise interfaces with dependency injection for easy testing and loosely coupled applications (what a mouthful).

With that in mind, I have developed a simple caching architecture that I can introduce into all new projects and has the following features:

  • Provides an ICache interface and associated cache adapter class through which all cache engines are accessed. This includes the .Net 4 MemoryCache, ASP.NET Web cache, and Windows AppFabric cache.
  • Provides an enhanced CacheProvider class (which implements ICacheProvider) that allows strongly typed cache access and a simple easy to use consistent API.
  • Allows selection of which cache engine to use via configuration.
    • The currently supported cache mechanisms are
      • Memory
      • Web
      • AppFabric
  • Fully supports dependency injection with everything already wired up. My current organisation standardises on Microsoft's Unity for Dependency Injection so that is what this library uses.
    • Container.Resolve<ICacheProvider>();

is all you need to do to access the cache mechanism.

  • Provides simple logging diagnostics (again via dependency injection so its easy to change) so that you can track whats going on.

Before I dive into the details, here is some example code to use this library and caching.

  1: var cacheProvider = AppServices.Resolve<ICacheProvider>();
  2: 
  3: Console.WriteLine("Getting Some Data.");
  4: var data = cacheProvider.Get<SomeData>("cache-key", DateTime.Now.AddSeconds(5), () =>
  5: {
  6:    Console.WriteLine("... => Adding data to the cache... ");
  7:    var someData = new SomeData() { SomeText = "cache example1", SomeNumber = 1 };
  8:    return someData;
  9: });

The preceding code sample first resolves our cache provider using the Unity container. Note that this is not an ICache instance (which we could also resolve and use directly), but rather a higher level ICacheProvider instance. This provides a more advanced and easy to use API for caching.

Then we simply try and retrieve the item (of type SomeData) from the cache passing in a cache key, the expiry time of the cached data, and an anonymous function as the last argument.

The anonymous function is only called if the data is not present in the cache. The return data of the anonymous function is placed into the cache using the cache key and expiry date/time, then returned to the caller.

The library comes with some simple example code to allow you to get up and running very quickly. If you want to use it, you can download it from here.

For those who are interested in more detail, then read on.

The design of the library is fairly simple.

The solution itself consists of 4 projects as shown below:

clip_image001

Glav.CacheAdapter.Core contains all the necessary interfaces and an implementation of the MemoryCache (which implements ICache). There are multiple cache adapters (memory, web and AppFabric) which implement ICache. The ICache interface has basic Get/Remove methods to manipulate the cache. To facilitate a higher level API with easier usage and without introducing extra these methods into ICache (thus forcing each ICache adapter implementation to have to implement these methods), there is also a ICacheProvider interface which has the enhanced methods for retrieving items from the cache and automatically inserting them if the data items do not exist in the cache. Methods such as

  1: Get<T>(string cacheKey, DateTime expiryTime, GetDataToCacheDelete)

Both Glav.CacheAdapter.Web and Glav.CacheAdapter.Distributed contain implementations of the ICache interface for ASP.NET and Windows AppFabric respectively.

clip_image002

Obviously, in order to use Windows AppFabric caching, you must have that installed on the machines that will utilise it. The library contains 2 core assemblies from Windows AppFabric that allow it to compile and reference the required functionality. If you enable AppFabric in the configuration of the library without it being installed and try to utilise caching, this will obviously fail.

clip_image003

Finally, to glue all this together a project called the Glav.CacheAdapter.CacheBootstrap will register the correct cache implementation into the service container based on the supplied configuration. The .Net 4 MemoryCache is the default if no configuration or an unrecognised configuration is supplied.

So that’s it. Download it and give it a try if you want a nicely abstracted and pre-packaged cache solution. I would welcome any feedback.

11 Comments

Comments have been disabled for this content.