In general, if we were to add some manual caching to our system, there are some steps we need to take:
1. Try to get that value from cache.
2. Check if the value was retrieved.
3. If it wasn’t, get it from database and add it to cache.
4. Handle the expiration time.
5. Return the value.
Memcache will do all of that in just a line, freeing you from having to repeat the same steps all the time:
public List<AdminProfile> GetAdminProfiles() { MemcachedItem<List<AdminProfile>> profiles = new MemcachedItem<List<AdminProfile>>("adminProfiles", cacheLifeTime, memcacheExpiryTime, cacheClient, () => noncachedObject.GetProfiles()); return profiles.Value; }
Well, maybe I lied a little bit, as you need other preparations to make it work, but the idea continues the same. When you create a memcache object, you make it “of a type” courtesy of .Net Generics, that is just to set the space of memory required (type of object). Then on instantiating it you will set its cache key, life time (time until the item should be refreshed but still cached), expiry time (time in which the cached item will just stop being cached) and, more importantly, where to get the item from when required (to refresh it or to load it for the first time).
You can also set those using the memcache object properties and methods, just notice that once you have it set up you only need to get its “value” to get the data.
Handling expiration
I didn’t show on the previous example how were these variables declared and prepared for its use, so let’s get into that now:
private readonly TimeSpan cacheLifeTime; private readonly TimeSpan memcacheExpiryTime; public MyClassInstantiation(){ string cachedItemLifeSpan = WebConfigurationManager.AppSettings["MemcachedItemRefreshAfterSeconds"]; int refreshInSeconds; cacheLifeTime = Int32.TryParse(cachedItemLifeSpan, out refreshInSeconds) ? TimeSpan.FromSeconds(refreshInSeconds) : new TimeSpan(0, 0, 1, 0); string cachedItemExpirySpan = WebConfigurationManager.AppSettings["MemcachedItemExpireAfterSeconds"]; int expireInSeconds; memcacheExpiryTime = Int32.TryParse(cachedItemExpirySpan, out expireInSeconds) ? TimeSpan.FromSeconds(expireInSeconds) : new TimeSpan(0, 0, 2, 0); }
That’s just an example of what we could do setting their values on the webconfig, we could even add different keys for different type of cached items (some being refreshed after 5 minutes, some in 15, or maybe one hour…).
Giving SRP to cache class
Also, we want to make sure that we create some “caching layer” making sure that the class caching is not the same as the class retrieving the object, so that each class will have its own responsibility and specialization. That is why cached classes will have a brother with memcache, the required values/constants declared and all of it specialized on caching:
public class AdminProfilesCached : IAdminProfiles { private IAdminProfiles noncachedAdminProfiles; private static IMemcachedClient cacheClient; private readonly TimeSpan cacheLifeTime; private readonly TimeSpan memcacheExpiryTime; public AdminProfilesCached(IAdminProfiles databaseDao, IMemcachedClient memcachedClient) { noncachedAdminProfiles = databaseDao ?? new AdminProfiles(); cacheClient = memcachedClient ?? cacheClient ?? new MemcachedClient(); } public List<AdminProfile> GetProfiles() { MemcachedItem<List<AdminProfile>> profiles = new MemcachedItem<List<AdminProfile>>("adminProfiles", cacheLifeTime, memcacheExpiryTime, cacheClient, () => noncachedAdminProfiles.GetProfiles()); return profiles.Value; } public void RemoveUser(int id) { noncachedAdminProfiles.RemoveUser(id); } public void AddUser(int id) { noncachedAdminProfiles.AddUser(id); } }
Notice that our caching class only has the duty to cache things, it will be easily replaceable with its non-cached brother in case we want to unit test it, it can even miss caching what it’s not going to be caching just moving the call to the brother class, and more importantly, it frees the brother class (where the access to the database is being made) from knowing anything about the caching system, allowing the caching system to be independent of the data-access system.
Overview
Just an overview of this example to get the idea:
public interface IAdminProfiles { List<AdminProfile> GetProfiles(); void AddUser(int id); void RemoveUser(int id); } public class AdminProfilesCached : IAdminProfiles { private IAdminProfiles noncachedAdminProfiles; private static IMemcachedClient cacheClient; private readonly TimeSpan cacheLifeTime; private readonly TimeSpan memcacheExpiryTime; public AdminProfilesCached(IAdminProfiles adminProfile, IMemcachedClient memcachedClient) { // Cacheclient is an expensive instantiation. Use the static or provided version if you can. noncachedAdminProfiles = adminProfile ?? new AdminProfiles(); cacheClient = memcachedClient ?? cacheClient ?? new MemcachedClient(); string cachedItemLifeSpan = WebConfigurationManager.AppSettings["MemcachedItemRefreshAfterSeconds"]; int refreshInSeconds; cacheLifeTime = Int32.TryParse(cachedItemLifeSpan, out refreshInSeconds) ? TimeSpan.FromSeconds(refreshInSeconds) : new TimeSpan(0, 0, 1, 0); string cachedItemExpirySpan = WebConfigurationManager.AppSettings["MemcachedItemExpireAfterSeconds"]; int expireInSeconds; memcacheExpiryTime = Int32.TryParse(cachedItemExpirySpan, out expireInSeconds) ? TimeSpan.FromSeconds(expireInSeconds) : new TimeSpan(0, 0, 2, 0); } public List<AdminProfile> GetProfiles() { var profiles = new MemcachedItem<List<AdminProfile>>("adminProfiles", cacheLifeTime, memcacheExpiryTime, cacheClient, () => noncachedAdminProfiles.GetProfiles()); return profiles.Value; } public void RemoveUser(int id) { noncachedAdminProfiles.RemoveUser(id); } public void AddUser(int id) { noncachedAdminProfiles.AddUser(id); } } public class AdminProfiles : IAdminProfiles { public List<AdminProfile> GetProfiles() { return null; } public void RemoveUser(int id) { } public void AddUser(int id) { } } public class AdminProfile { }