Медиатр: уменьшение количества 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 вещи:

  1. делают ли мои обработчики слишком много; а также
  2. если это так, могу ли я рефакторинг некоторых из поведения в отдельном классе

Например, в опубликованном вами коде обработчика есть клиент кеша, что может означать, что ваш обработчик делает 2 вещи:

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

MediatR имеет концепцию поведения, которая позволяет вам решать сквозные проблемы в одном месте; это потенциально применимо к кешированию, ведению журналов и обработке исключений. Если вы знакомы с промежуточным ПО ASP.NET Core, они придерживаются той же концепции, что и каждое поведение:

  1. текущий запрос (или запрос на языке MediatR); а также
  2. следующий элемент в конвейере, который может быть другим поведением или обработчиком запроса

Давайте посмотрим, как мы могли бы извлечь логику кэширования в поведении. Теперь вам не нужно следовать этому примеру к букве 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();

И вот, у нас это есть, теперь кеширование является сквозной задачей, реализация которой находится в одном классе, что делает его легко изменяемым и тестируемым.

Мы можем применить один и тот же шаблон для разных вещей и сделать так, чтобы обработчики отвечали только за бизнес-логику.

Другие вопросы по тегам