Question

I have gone back and forth on this issue several times.

On one hand, you could argue, a repository's single responsibility is to manage the persistent state of an entity, and the consuming application is the one that knows whether it will accept a stale value or not in exchange for performance.

However, if you are dealing with an out of process cache, and many different applications/servers all need that same cached value, putting the caching code in the repository is a convenient place for, and would help avoid cache duplication. Since there is, at that point a single cache acting as a single source of truth, and acting as a buffer for the DB, it seems more appropriate for a repository to contain caching code.

Does caching code belong in a repository?

What about specifically an out of process cache (such as redis) acting as a buffer to the database?

If caching code does not belong in a repository, where should it reside if many desperate pieces of code need that same value?

Was it helpful?

Solution

You can use a proper design to achieve the best of both worlds. You could use the decorator pattern as Vincent suggested.

enter image description here

In this design you have CachingRepository that implements your repository interface and has an instance of the repository that it delegates calls to (when there is a cache miss). This design separates the concerns of caching and fetching entities, but invisibly to the client of Repository.

The psuedocode of CachingRepository would be:

getFoo(id): Foo
  if cache contains id
    return cached Foo
  else
    call getFoo on delegate
    store result of getFoo in cache
    return result

In your composition root you would instantiate a CachingRepository with a DbRepository as its delegate

Repository repo = new CachingRepository(new DbRepository(...));

OTHER TIPS

I think it generally depends on the nature of the data being cached and the purpose. Some caches should be on the repository. These are the caches that are designed to provide accelerated access to consistent, accurate values. Low-level examples would be disk caches, DB Query plans, even values from a Redis cache that are intended to be a fast reflection of the actual, current values.

Application caches are different beasts and don't have to live in the actual repository or be shared. An example might be a list of ticker codes held at the web server (or even browser local storage) good for a day.

Real-time ticker prices may be somewhere in the middle - persisted to a "proper" repository but at the same time made available to applications in a faster, if less resilient or consistent way (again, like a copy in Redis or such like).

I think the summary is that you need different techniques for different circumstances, hence your back & forth trying to get to the "one true answer" that isn't there.

One can separate it so that the database or "repository" has no idea that data is being cached. This is a clean solution and can be implemented as needed across the repository's query methods. As @Vincent suggests this is a decorator type of pattern. Pretend IQuery is the repository command to get the data (TOut). (TIn) can be some sort of object that represents a criteria or parameters used by the query command.

If the data isn't in the cache we use the repository to retrieve it.

public class QueryCaching<TIn, TOut> : IQuery<TIn, TOut>
    {
        private readonly ICache _cache;
        private readonly IQuery<TIn, TOut> _command;
        private readonly string _key;

        public QueryCaching(string key, ICache cache, IQuery<TIn, TOut> command)
        {
            _command = command;
            _key = key;
            _cache = cache;
        }

        public TOut Execute(TIn input)
        {
            TOut obj;
            if (_cache.Get(_key, out obj))
            {
                Debug.WriteLine(string.Format("Cache hit for key {0}", _key));
                return obj;
            }

            Debug.WriteLine(string.Format("Cache miss for key {0}", _key));
            obj = _command.Execute(input);
            _cache.Set(_key, obj);

            return obj;
        }
    }
Licensed under: CC-BY-SA with attribution
scroll top