I would like to wrap a repository inside another repository that will handle caching while internally using the passed in repository. This way my caching logic can be completely separate from the repository implementation. This pattern would also allow me to easily change from memory cache to distributed cache, that is I could have different caching repositories that use different cache types so I can plug them in depending on the environment. On Azure I could use distributed cache but on a single server I could use memory cache for example.
public sealed class CachingFooRepository : IFooRepository
{
private IFooRepository repo;
private IMemoryCache cache;
public CachingFooRepository(IFooRepository implementation, IMemoryCache cache, IFooCachingRules)
{
if ((implementation == null)||(implementation is CachingFooRepository))
{
throw new ArgumentException("you must pass in an implementation of IFooRpository");
}
repo = implementation;
this.cache = cache;
}
public async Task<bool> Save(IFooItem foo)
{
// TODO throw if foo is null
bool result = await repo.Save(user);
string key = "Key-" + foo.Id.ToString();
cache.Remove(key);
return result;
}
public async Task<IFooItem> Fetch(int fooId)
{
string key = "Key-" + fooId.ToString();
object result = cache.Get(key);
if(result != null) { return (foo)result; }
foo = await repo.Fetch(fooId);
if(foo != null)
cache.Set(key, foo);
return foo;
}
}
Obviously while CachingFooRepository implements IFooRepository, I must make sure that a different implementation of IFooRepository is passed into the constructor for CachingFooRepository since it isn't a real implementation of IFooRepository but depends on a real implementation.
This example is simplified, pseudo-ish code, whether to cache and how long to cache can be passed in as IFooCachingRules or IOptions or something like that.
So my question is how can I reigster the services in such a way that things that depend on IFooRepository will get an instance of CachingFooRepository, but CachingFooRepository will get some other implementation of IFooRepository such as SqlFooRepository? I would like to keep other classes depending only on IFooRepository, I don't want to make anything depend specifically on CachingFooRepository.
Is this possible, feasible, a good idea, a bad idea?
There's a name for the pattern you are using. It's called: The Decorator pattern.
Using the decorator pattern is an excellent idea, because it allows you to make add functionality, without having to make changes to any existing part of the system. In other words, you are able to adhere to the Open/closed principle.
No, there's no easy way do this with ASP.NET Core's built-in DI container. You should use one of the mature existing DI libraries for .NET to do this. The three libraries with the best support for applying the decorator pattern are Autofac, StructureMap and Simple Injector.