Медиатр: уменьшение количества DI-объектов
У меня много команд и запросов, и большинству из них нужны одинаковые интерфейсы, предназначенные для разных целей. Можно ли как-нибудь уменьшить этот беспорядок, который нужен каждому моему обработчику, и он повторяется снова и снова?
public class GetCoinByIdQueryHandler : IRequestHandler<GetCoinByIdQuery, CoinModel>
{
private readonly EventsContext context;
private readonly ICacheClient cache;
private readonly ILogger logger;
private readonly IMapper mapper;
private readonly Settings settings;
public GetCoinByIdQueryHandler(
EventsContext context, ICacheClient cache, ILogger logger,
IMapper mapper, IOptions<Settings> settings)
{
this.context = context;
this.cache = cache;
this.logger = logger;
this.mapper = mapper;
this.settings = settings.Value;
}
}
Возможно, это не имеет прямого отношения к Mediatr, но я ищу более элегантный способ просто свести все обычные к, возможно, ONE DI'ed-param.
Я использую Autofac в качестве моего DI-контейнера, если это имеет какое-либо значение.
РЕДАКТИРОВАТЬ: возможно, с базовым классом, который наследуют все обработчики от и в базовом классе, получить доступ ко всем интерфейсам и установить их как свойства базового класса, но я понятия не имею, как этого добиться.
РЕДАКТИРОВАТЬ 2: Autofac имеет инъекцию свойств, но кажется, что это неправильный подход, поэтому люди, которые используют Mediatr, как вы справляетесь с повторением себя снова и снова. Кажется, что каждый проект с открытым исходным кодом, использующий Mediatr, не решает проблему повторения.
1 ответ
Когда я оказываюсь в ситуации, когда несколько обработчиков имеют много общих зависимостей, я смотрю на 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();
И вот, у нас это есть, теперь кеширование является сквозной задачей, реализация которой находится в одном классе, что делает его легко изменяемым и тестируемым.
Мы можем применить один и тот же шаблон для разных вещей и сделать так, чтобы обработчики отвечали только за бизнес-логику.