Often is the case when you’re creating an application (be it web or not) the need for caching can become a critical part of software design. You can be faced with a myriad of designs and mechanisms used to store objects, and the specifics of how each container works from one implementation to the next can differ greatly.
What we can do thoough is abstract the implementation away and provide a generic, type-safe way of accessing a cache of objects, as well as provide additional expiration functionality and grouping. Basically, we want to be able to call:
Person me = Cache.Fetch(“me”);
…or:
Person me = Cache.Fetch(“me”, () => new Person(“Matthew”, “Abbott”));
At its most basic, the caching framework should allow us to get objects, and if objects don’t exist, go ahead and create them and store them automatically. It should be just as easy for single and multiple objects:
var people = Cache.FetchAll(() => GetAllPeople());
A brief description of the major types involved with the framework:
ICacheProvider – The interface to define a cache provider.
CachedItem – An item stored in a cache.
CachingGroup – The type used to group sets of items.
CacheBinding – A binding between a type, a CachingGroup and a specific provider.
CacheManager – A manager type used for accessing provider functions.
Cache – The generic type used for accessing caches.
IExpirable – The interface to define an expirable item.
As a big fan of the Provider model, it made sense to design the framework as such, creating the need for a cache provider interface, or ICacheProvider. The interface need only provide the methods used for managing the cache, that being Fetch, FetchAll, Store, Remove and RemoveAll. Now there could be many overloaded versions of each method, but we can handle the overloads later with a different type, all the provider is really concerned with is the actual operation. Here is my ICacheProvider interface:
public interface ICacheProvider
{
IEnumerable<T> FetchAll<T>(CachingGroup cachingGroup, Func<IEnumerable<T>> getFunction, Func<T, string> keyFunction, Func<T, ExpirationSettings> expiryFunction);
T Fetch<T>(string key, CachingGroup cachingGroup, Func<string, T> getFunction, Func<string, T, ExpirationSettings> expiryFunction);
void Store<T>(string key, T value, CachingGroup cachingGroup, ExpirationSettings expirySettings);
void Store<T>(IEnumerable<T> items, CachingGroup cachingGroup, Func<T, string> keyFunction, Func<string, T, ExpirationSettings> expiryFunction);
void Remove<T>(string key, CachingGroup cachingGroup);
void RemoveAll<T>(CachingGroup cachingGroup);
}
Code language: JavaScript (javascript)
A look at the methods:
FetchAll(..) – Used for fetching multiple items. If no items exist in the cache, the getFunction delegate can be used to get some instances to store, and in the same vein, the keyFunction and expiryFunction delegates can be used for creating keys and expiration settings respectively.
Fetch(…) – Used for fetching a single item. The key is required, and we are explictly trying to find a specific item.
Store(string key, T value, …) – Used to storing a single instance with the specified key.
Store(IEnumerable items, …) – Used for storing multiple instances. As with FetchAll(), a keyFunction can be provided for generating an item specific key.
Remove(…) and RemoveAll(…) – Used for removing items from the cache.
A colleague asked about implementing a Clear() method, this current version does not implement such a method, but it could be considered for a later version.
When an item is requested using the generic Cache type (this itself is static), a call is made to the cache manager to firstly identify the cache provider being used for the requested type, and also the caching group (if applicable. In its simplest:
The cache manager maintains an internal list of bindings which govern the behaviour of how the cache manager interacts with specific providers. For example, you could choose to store Person items in one cache, but store Person items with a specific CachingGroup in another.
So we can see that its easy to grab items from the cache, and Storing items is much the same. The model allows for a fine amount of control over how each cache behaves, as the actual storing and retrieving of items is down to the provider itself, your code doesn’t need to worry about that.
One thing the provider interface doesn’t deal with specifically is item (or group) expiry. With the current version of the framework, our base implementation, CacheProviderBase provides methods for determining whether or not an item (or group) has expired. When an item is pulled from the cache, its expiry is updated and then checked. If the item is valid, we can return it, otherwise we return the default for the required type.
protected virtual Tuple<bool, T> ResolveValue<T>(CachedItem<T> cachedItem)
{
Throw.IfArgumentNull(cachedItem, "cachedItem");
if (cachedItem.CachingGroup != null) {
cachedItem.CachingGroup.UpdateExpiry();
}
cachedItem.UpdateExpiry();
if (cachedItem.CachingGroup != null && cachedItem.CachingGroup.HasExpired()) {
Remove<T>(cachedItem.Key, cachedItem.CachingGroup);
return new Tuple<bool, T>(false, default(T));
}
if (cachedItem.HasExpired()) {
Remove<T>(cachedItem.Key, cachedItem.CachingGroup);
return new Tuple<bool, T>(false, default(T));
}
return new Tuple<bool, T>(true, cachedItem.Value);
}
Code language: PHP (php)
The return type Tuple is a wrapper type designed on the same principle of .NET 4.0′s Tuple. This is a v2.0/3.0/3.5 compatible project, when it gets upgraded to .NET 4.0, the project-domain Tuple type can be removed in favour of .NET’s own implementation.
Where are you likely to use this in actual implementations? At work we are busy working away converting our existing website into a database driven, content-managed website. As content and meta information doesn’t change often we can get away with caching data in the HttpRuntime.Cache. To this end, we’ve implemented a HttpRuntimeCacheProvider which does just that. In fact the current version of the framework creates a HttpRuntimeCacheProvider as the default provider for web applications (we also have a HashtableCacheProvider and a couple of others pre-built).
I’ve attached the project for you’re viewing, let me know what you think and how it can be improved.
CachingFramework.rar