Мы используем архитектуру CQRS в нашем приложении, то есть у нас есть ряд классов, реализующих ICommand
, и для каждой команды есть обработчики: ICommandHandler<ICommand>
. То же самое относится к извлечению данных - у нас есть IQUery<TResult>
с IQueryHandler<IQuery, TResult>
. Довольно часто в наши дни.
Некоторые запросы используются очень часто (для нескольких выпадающих страниц на страницах), и имеет смысл кэшировать результат их выполнения. Итак, у нас есть декоратор вокруг IQueryHandler, который кэширует выполнение запросов.
Запросы реализуют интерфейс ICachedQuery
, а декоратор кэширует результаты. Вот так:
public interface ICachedQuery {
String CacheKey { get; }
int CacheDurationMinutes { get; }
}
public class CachedQueryHandlerDecorator<TQuery, TResult>
: IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
private IQueryHandler<TQuery, TResult> decorated;
private readonly ICacheProvider cacheProvider;
public CachedQueryHandlerDecorator(IQueryHandler<TQuery, TResult> decorated,
ICacheProvider cacheProvider) {
this.decorated = decorated;
this.cacheProvider = cacheProvider;
}
public TResult Handle(TQuery query) {
var cachedQuery = query as ICachedQuery;
if (cachedQuery == null)
return decorated.Handle(query);
var cachedResult = (TResult)cacheProvider.Get(cachedQuery.CacheKey);
if (cachedResult == null)
{
cachedResult = decorated.Handle(query);
cacheProvider.Set(cachedQuery.CacheKey, cachedResult,
cachedQuery.CacheDurationMinutes);
}
return cachedResult;
}
}
Была дискуссия о том, должен ли мы иметь интерфейс по запросам или атрибуту. Интерфейс в настоящее время используется, потому что вы можете программно изменить ключ кеша в зависимости от того, что кэшируется. То есть вы можете добавить идентификатор сущности в кэш-ключ (т.е. иметь такие ключи, как "person_55", "person_56" и т.д.).
Конечно, проблема связана с недействительностью кэша (именованием и недействительностью кэша, а?). Проблема в том, что запросы не совпадают друг с другом с командами или сущностями. И выполнение одной команды (т.е. Модификация записи человека) должно отображать недействительные множественные записи кеша: запись человека и выпадающие имена людей.
На данный момент у меня есть несколько кандидатов для решения:
- У всех ключей кеша, записанных каким-либо образом в классе сущностей, отметьте объект как
ICacheRelated
и верните все эти ключи как часть этого интерфейса. И когда EntityFramework обновляет/создает запись, получите эти ключи кеша и аннулирует их. (Hacky!) - Команды должны быть недействительными для всех кешей. Вернее,
ICacheInvalidatingCommand
должен вернуть список ключей кеша, которые должны быть недействительными. И иметь декоратор наICommandHandler
, который приведет к аннулированию кеша при выполнении команды. - Не отключайте кеширование, просто установите короткие сроки службы кэширования (как короткий?)
- Магия beans.
Мне не нравится какой-либо из вариантов (возможно, помимо номера 4). Но я думаю, что вариант 2 - это тот, который я отдам. Проблема с этим, создание кеш-ключа становится беспорядочным, мне нужно иметь общее место между командами и запросами, которые умеют генерировать ключи. Другая проблема заключалась бы в том, что будет слишком легко добавить еще один кешированный запрос и пропустить недопустимую часть команд (или не все команды, которые должны быть недействительными, будут аннулированы).
Любые лучшие предложения?