Когда я нахожусь в ситуации, когда несколько обработчиков имеют много общих зависимостей, я смотрю на 2 вещи:
- не слишком ли много делают мои обработчики;и
- , если это так, могу ли я провести рефакторинг некоторых действий в отдельном классе
Например, в опубликованном вами коде обработчика есть клиент кеша, которыйможет означать, что ваш обработчик делает 2 вещи:
- выполнение бизнес-логики для извлечения монеты;и
- выполняя некоторую логику, возвращают уже кэшированную монету или кэшируют только что полученную
MediatR имеет концепцию поведения , которая позволяет вам обрабатыватьсквозные проблемы в одном месте;это потенциально применимо к кешированию, ведению журналов и обработке исключений.Если вы знакомы с промежуточным программным обеспечением ASP.NET Core, они придерживаются той же концепции, что и каждое поведение:
- текущий запрос (или запрос на языке MediatR);и
- следующий элемент в конвейере, который может быть или другим поведением или обработчиком запроса
Давайте посмотрим, как мы можем извлечь логику кэширования в поведении.Теперь вам не нужно следовать этому примеру для T, это на самом деле всего лишь одна из возможных реализаций.
Сначала мы определим интерфейс, который мы применяем к запросам, которые необходимо кэшировать:
public interface IProvideCacheKey
{
string CacheKey { get; }
}
Затем мы можем изменить GetCoinByIdQuery
для реализации этого нового интерфейса:
public class GetCoinByIdQuery : IRequest<CoinModel>, IProvideCacheKey
{
public int Id { get; set; }
public string CacheKey => $"{GetType().Name}:{Id}";
}
Далее нам нужно создать поведение MediatR, которое позаботится о кэшировании.При этом используется IMemoryCache
, предоставляемый в ASP.NET Core, исключительно потому, что я не знаю определения вашего ICacheClient
интерфейса:
public class CacheBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IProvideCacheKey, IRequest<TResponse>
{
private readonly IMemoryCache _cache;
public CacheBehavior(IMemoryCache cache)
{
_cache = cache;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
// Check in cache if we already have what we're looking for
var cacheKey = request.CacheKey;
if (_cache.TryGetValue<TResponse>(cacheKey, out var cachedResponse))
{
return cachedResponse;
}
// If we don't, execute the rest of the pipeline, and add the result to the cache
var response = await next();
_cache.Set(cacheKey, response);
return response;
}
}
Наконец, нам нужно зарегистрировать поведениес Autofac:
builder
.RegisterGeneric(typeof(CacheBehavior<,>))
.As(typeof(IPipelineBehavior<,>))
.InstancePerDependency();
И вот, у нас это есть, кеширование - теперь междисциплинарная задача, реализация которой находится в одном классе, что делает его легко изменяемым и тестируемым.применять один и тот же шаблон для разных вещей и назначать обработчики ответственными только за бизнес-логику.