Introduction
In today's world, application design should account for the ability to plug in the cross cutting concerns (like caching, logging and exception handling, etc.), to be able to scale out the application for future requirements without incurring additional maintenance costs.
Background
I was working on a client project recently and was tasked to take a look at performance tuning of the application and provide recommendations for scaling out the application. As part of the process, one of the things we have observed is that the specific implementation of cross-cutting concern (to cite here, Caching) is so tightly integrated into the business layer (usage HttpContext.Current.Cache
) thus preventing (if not preventing, making it harder requiring huge code change, testing effort, delayed timelines and last but not least re-deployment) to scale out the application.
The developers and designers have used ASP.NET cache (not that ASP.NET cache is bad rather it is not global / distributed cache), thus making the cache object to be duplicated on each web server in the farm. In this case, populating the data into cache is costly as the query that needs to be run is complex (infact very complex) and takes few seconds to execute. This has triggered me to write up an article that would help people out there on how they can create pluggable design.
I would like to thank Mr Anil Sistla (Architect from Virtusa) for the architectural idea and for continuous support in the solution implementation.
This solution puts forth how a IoC (Inversion of Control Container) can help you to get around these kind of dependencies and shows how you could have been able to scale out easily by switching the caching data provider layer with minimal configuration change.
In this sample, the IoC container I have used is StructureMap
. There are other IoC frameworks out there, evaluate and use one based on your requirement. This topic is not about specific Ioc, rather how you can use IoC to provide a pluggable design, and it is not just limited to addressing cross cutting concerns, rather the concept can be applied to any part of the application.
This sample uses Memcached Server, MemcachedClient
and ASP.NET cache.
Using the Code
Below is the code structure for the cache sample:
We have an interface named CacheProviders.Interfaces.ICache
. For demo purposes, this interface provides three basic operations SetItem
, GetItem
and RemoveItem
.
We have two implementations of this ICache
interface, one named CacheDataProviders.Memcached
and the other named CacheDataProviders.AspNet
.
AspNetCacheProvider
implementation of the Icache
interface:
public class AspNetCacheProvider : Providers.ICache {
public AspNetCacheProvider() {
}
bool Providers.ICache.SetItem(string key, object value) {
HttpRuntime.Cache.Insert(
key,
value,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Default,
null);
return true;
}
object Providers.ICache.GetItem(string key) {
object _item = HttpRuntime.Cache.Get(key);
return _item;
}
bool Providers.ICache.RemoveItem(string key) {
HttpRuntime.Cache.Remove(key);
return true;
}
}
MemCacheProvider
implementation of the Icache
interface:
public class MemCacheProvider : Providers.ICache {
private readonly MemcachedClient _cacheProviderClient;
public MemCacheProvider() {
_cacheProviderClient = new MemcachedClient();
}
bool Providers.ICache.SetItem(string key, object value) {
bool _stored = _cacheProviderClient.Store(StoreMode.Set, key, value);
return _stored;
}
object Providers.ICache.GetItem(string key) {
object _value = _cacheProviderClient.Get(key);
return _value;
}
bool Providers.ICache.RemoveItem(string key) {
bool _removed = _cacheProviderClient.Remove(key);
return _removed;
}
}
CacheDataProviders.MemCached
uses eniym.memcached
client to store data into memcached.
CacheDataProviders.AspNet
uses ASP.NET cache to store data.
CachingSample
application is the program that is consuming caching layer without knowing specific details of the CacheDataProvider
.
So, for development purposes, you might like to use AspNet
cache and for staging / production deployment, you might want to use distributed cache like Memcache
or may be your own cache provider (of course, make sure the respective CacheDataProvider
is tested).
We use the DI framework to create the necessary cache provider based on the environment (staging or production), this article uses StructureMap
DI framework as it is lightweight and easy to demonstrate (There are other DI frameworks available in the market for .NET like Spring.Net, Microsoft's unity, etc. Each has its own merits and demerits, it is upto the developer/designer to choose the appropriate DI framework based on their requirements).
Below is the configuration part of the StructureMap
in App.config file, currently it is set to MemCacheProvider
. We can flip the plugin from MemCached
to ASP.NET easily in the configuration file, and the application will start adopting to the new cache provider in this case ASP.NET cache. If the current cache provider has any limitations, then we can easily scale out the application to a different cache provider by just changing the one step in configuration.
<StructureMap>
<PluginFamily Type="CacheProviders.Interfaces.ICache"
Assembly="CacheProviders.Interfaces"
DefaultKey="DefaultCache">
<Plugin Assembly="CacheProviders.MemCached"
Type="CacheProviders.Memcached.MemCacheProvider"
Scope="Singleton"
ConcreteKey="DefaultCache" />
</PluginFamily>
</StructureMap>
Disclaimer
Sample code provided in the article is only for demo purposes, and is not ready for production/deployment purposes. It is the responsibility of the reader to evaluate the risk before integrating the code with his/her application.