6

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?

  • 1
    Caching can often be another layer on top of the actual source as a Decorator, e.g. you have a FooDao interface which is implemented by DatabaseFooDao and RedisFooDao, the latter delegating to the former if the value is not in cache. Whether the caching should be present at the data access level or at the domain level depends on what you cache, in my opinion. – Vincent Savard Aug 9 '17 at 18:20
  • @VincentSavard I believe you have high enough rep to put this information in an answer if you desire and have time – TheCatWhisperer Aug 9 '17 at 18:53
  • 3
    If it helps your thought process, caching is an implementation detail. – Robert Harvey Aug 9 '17 at 19:21
10

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(...));
  • 7
    What you show here is actually an implementation of the decorator pattern. – Bart van Ingen Schenau Aug 9 '17 at 19:58
  • 2
    Yes it is. The Decorator abstraction doesn't have to be an abstract class - it just has to define a common interface. In this case, this is defined with the Repository interface, while your CachingRepository is a concrete decorator class. – Filip Milovanović Aug 10 '17 at 7:16
  • 1
    @FilipMilovanović Why isn't the above a Repository + Chain of Responsibility pattern? The delegate is what made me think of it. – kaneda Mar 4 '18 at 0:58
  • 2
    @kaneda: Patterns are sometimes structurally similar; what differentiates them is the "intent", the core problem thy are trying to solve. With the Chain of Responsibility pattern, you have a chain of objects and you want to send a message/event down the structure (which can mean just calling a method on some top-level object), but have no idea which of the objects, if any, is going to handle it. Some objects may simply do nothing but pass the call along. Often, there will be a single object that will handle the event then stop it from propagating further. (continues below...) – Filip Milovanović Mar 4 '18 at 10:14
  • 1
    (... continued) The part that makes up the Decorator here is the IRepository + CachingRepository + any other class that implements IRepository, and thus can be wrapped by the CachingRepository. The idea behind Decorator is that it dynamically adds functionality on top of an object, and here, that functionality is caching. There may be more then one level of decoration, but, in contrast to the Chain of Responsibility, usually every object in the structure does some meaningful work, i.e. work is divided across objects. (continues...) – Filip Milovanović Mar 4 '18 at 10:14
1

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.

0

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;
        }
    }

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.