Кэширование хранилища ASP.NET

Я начинаю работать с инфраструктурой кэширования для моего сайта ASP.NET MVC. Проблема в том, что я не могу найти разумное место для кэширования данных (кроме "везде" )

Сейчас моя архитектура выглядит так:

Контроллер → Сервисный уровень → Репозиторий. Репозиторий использует Linq для SQL для доступа к данным.

В репозитории раскрываются общие методы, такие как Insert, GetById и GetQueryable, который возвращает IQueryable, который может улучшить уровень обслуживания.

Мне нравится идея кэширования в слое репозитория, так как уровень сервиса не должен заботиться о том, откуда поступают данные. Проблема, однако, связана с недействительностью кэша. Уровень сервиса имеет больше информации о том, когда данные становятся устаревшими, чем репозиторий. Например:

Предположим, что у нас есть таблица Users и таблица Orders (канонический пример). Уровень сервиса предлагает такие методы, как GetOrder (int id), который будет вызывать уровень репозитория:

public Order GetOrder(int id)
{
    using(var repo = _repoFactory.Create<Order>())
    {
        return repo.GetById(id)
    }
}

или

repo.GetQueryable(order => order.Id == id && order.HasShipped == false).Single();

Если мы кэшируем на уровне репозитория, похоже, что это будет очень ограниченно, зная, когда данные этого заказа изменились. Предположим, пользователь удален, в результате чего все их заказы будут удалены с помощью CASCADE. Уровень сервиса может привести к аннулированию кеша ордеров, поскольку он знал, что пользователь был просто удален. Однако репозиторий (поскольку это подразделение работы) не будет знать. (Игнорируйте тот факт, что мы не должны запрашивать заказы для удаленного пользователя, так как это просто пример).

В других ситуациях, когда я думаю, это показывает себя. Предположим, мы хотим получить все заказы пользователей:

repo.GetQueryable(order => order.UserId == userId).ToList()

Репозиторий может кэшировать результаты этого запроса, но, если добавлен другой порядок, этот запрос больше недействителен. Тем не менее, только уровень обслуживания знает об этом.

Также возможно, что мое понимание уровня репозитория неверно. Я рассматриваю его как фасад вокруг источника данных (т.е. Переход от L2SQL к EF к чему-либо, уровень сервиса не знает о базовом источнике).

Ответ 1

Реально, вам понадобится еще один слой; уровень кэширования данных. Он будет использоваться вашим уровнем обслуживания при запросе данных. После такого запроса он решит, имеет ли он данные в кеше или если ему нужно запросить соответствующий репозиторий. Аналогично, ваш уровень сервиса может рассказать об этом новом уровне кэширования данных о недействительности (удаление конкретного пользователя и т.д.).

Что это может означать для вашей архитектуры, так это то, что ваш слой кэширования данных реализует те же интерфейсы (ы), что и ваши репозитории. Достаточно простая реализация будет кэшировать данные по типу сущности и ключу. Однако, если вы используете более сложную ORM за кулисами (NHibernate, EF 4 и т.д.), У нее должно быть кэширование как опция для вас.

Ответ 2

Вы можете поместить событие на объекты, возвращаемые вашими репозиториями, и предоставить репозиторию подписывать недействительность кэша обработчику.

Например,

  public class SomethingRepository{
        public Something GetById(int id){
            var something = _table.Single(x=>x.id==id);
            something.DataChanged += this.InvalidateCache;
            return something;
        }

        public void InvalidateCache(object sender, EventArgs e){ 
            // invalidate your cache 
        }
  }

И ваш объект Something должен иметь событие DataChanged и некоторый общедоступный метод для вашего уровня обслуживания, чтобы вызвать его запуск. Как,

  public class Something{
       private int _id;
       public int Id{
         get { return _id; }
         set {
            if( _id != value ) 
            {
              _id = value;
              OnDataChanged();
            }
         }
       }
       public event EventHandler DataChanged;
       public void OnDataChanged(){
            if(DataChanged!=null)
                 DataChanged(this, EventArgs.Empty);
       }
  } 

Таким образом, весь ваш уровень обслуживания должен знать, что данные изменяются, а репозиторий обрабатывает недействительность кеша.

Я также предлагаю вам воспользоваться рекомендацией Ventaur и поместить логику недействительности кеша в отдельную службу. Вам не нужно заходить так далеко, чтобы создать отдельный "слой кэширования данных", но логика была бы более чистой, если бы она содержалась в другом классе.