2

I would like to have a caching solution for a variety of function calls.

All of the function calls fit the following signature

public ResponseType ProcessRequest(RequestType request); 

About half the time the cache key can be applied very simply, in a generic manner by pulling a key from the serialized string. The other half the time the cache key will need to be calculated very specifically for the data in the request, using specific fields that only that request includes. If the response should be cached is almost always certainly done based on the data of the response object. I decided down an interceptor because it can be turned on and off and be wrapped around the function so that the function call itself has no clue it's happening, and this needs to be applied to a lot of different function calls all over the place.

here is what I have so far:

public interface ICacheProvider
{
    object GetCachedItem( string cacheKey );
    void AddItemToCache( object item, string key );
}

public class CacheProvider
{
    private readonly ConcurrentDictionary< string, object > _cacheDictionary;
    private CacheProvider() { _cacheDictionary = new ConcurrentDictionary< string, object >(); }
    public object GetCachedItem( string cacheKey ) { return _cacheDictionary.ContainsKey( cacheKey ) ? _cacheDictionary[ cacheKey ] : null; }
    public void AddItemToCache( object item, string key ) { _cacheDictionary.TryAdd( key, item ); }
}

public interface ICacheKey
{
    string GetCacheKey();
}

public interface IIsCacheable
{
    bool IsCacheable();
}

public class CacheInterceptor : IInterceptor
{
    private readonly ICacheProvider _cacheProvider;
    public CacheInterceptor( ICacheProvider cacheProvider ) { _cacheProvider = cacheProvider; }

    public void Intercept( IInvocation invocation )
    {
        var request = invocation.Arguments.First() as ICacheKey;
        var requestCacheKey = request.GetCacheKey();

        var cachedResponse = _cacheProvider.GetCachedItem( requestCacheKey );

        if( cachedResponse != null )
            invocation.ReturnValue = cachedResponse;
        else
        {
            invocation.Proceed();

            var response = invocation.ReturnValue as IIsCacheable;
            if( response.IsCacheable() )
                _cacheProvider.AddItemToCache( response, requestCacheKey );
        }
    }
}

There is also currently the concept of a Transaction object which has a Request and Response object. I've thought about applying the concept of caching to that but would be akward with a method interceptor because i would have to fetch the transaction from the transaction manager which is a weird static dependency thing for a lot of other stuff and i'm trying to get rid of that as well.

The problems with my proposed solution I would like to address: - I don't think this example conforms to the SOLID principles which is why I think I many of the other concerns. Please tell me why and how one would fix this?

  • having to implement two separate interfaces for cacheing ICacheKey/IIsCacheable seems like a bit much.
  • My request and response object types have to be aware of the cache solution? on one hand this makes sense because they know how the data is structured. On the other hand the idea of caching is a seperate concern then how the data is stored and should therefore be in another section?
  • Why not merge IInvocation, ICacheKey and IIsCacheable? They seem to be all called from same place, so it doesn't break ISP. – Euphoric Mar 29 '16 at 18:50
  • 1
    Also the part invocation.Arguments.First() doesn't look good. If you merge the interfaces, then it will be invocation's job to pick the right key. No need to complicate the interceptor with logic for picking the right argument that knows the key. – Euphoric Mar 29 '16 at 18:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.