2

I have a lot of commands and queries and most of them need same interfaces DI'ed to do different things. Is it possible to some how reduce this clutter that each and every one of my handler needs and it is repeated over and over?

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;
    }
 }

This may not be directly related to Mediatr but I am looking for a more elegant way of just reducing all the common ones to maybe ONE DI'ed param.

I am using Autofac as my DI container if it makes any difference.

EDIT: possibly having base class that all the handlers inherit from and in the base class get access to all the interfaces and set them as properties on the base class, but I have no idea how to achieve this.

EDIT 2: Autofac has property injection but that seems like it is not the right approach, so people who are using Mediatr, how are you handling of repeating yourself over and over. Every open source project that uses Mediatr that I have seen, seem to not address the repeating yourself problem.

Steven
  • 166,672
  • 24
  • 332
  • 435
Zoinky
  • 4,083
  • 11
  • 40
  • 78
  • @TheGeneral Mediatr is great for request/response but as you can see, each and every commandhandler will require me to repeat the above over and over. After posting this question, I realized that AutoFac has Property Injection capabilities but that is going into another rabbit hole. I would like to continue to use Mediatr but with caution to the above problems. – Zoinky Dec 29 '18 at 03:24
  • Here are some interesting articles for you to read: [Facade Services refactoring](https://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/), [the problem with logging](https://blog.codinghorror.com/the-problem-with-logging/), [do I log too much](https://stackoverflow.com/a/9915056/264697), and [why you shouldn't depend on `IOptions`](https://simpleinjector.org/aspnetcore#working-with-ioptions-t). – Steven Dec 29 '18 at 12:15
  • @steven thanks, very helpful articles – Zoinky Dec 29 '18 at 14:53

1 Answers1

2

When I find myself in the situation where several handlers have many common dependencies, I look at 2 things:

  1. whether my handlers are doing too much; and
  2. if it's the case, whether I can refactor some of the behavior in a separate class

As an example, in the handler code you posted, there's a cache client, which could possibly mean your handler does 2 things:

  1. executing the business logic to retrieve the coin; and
  2. doing some logic do return an already cached coin, or caching the one you just retrieved

MediatR has the concept of behaviors which allow you to handle cross-cutting concerns in a single place; this is potentially applicable to caching, logging and exception handling. If you're familiar with ASP.NET Core middlewares, they follow the same concept, as each behavior is given:

  1. the current request (or query in MediatR lingo); and
  2. the next item in the pipeline, which can be either another behavior or the query handler

Let's see how we could extract the caching logic in a behavior. Now, you don't need to follow this example to a T, it's really just one possible implementation.

First, we'll define an interface that we apply to queries that need to be cached:

public interface IProvideCacheKey
{
    string CacheKey { get; }
}

Then we can change GetCoinByIdQuery to implement that new interface:

public class GetCoinByIdQuery : IRequest<CoinModel>, IProvideCacheKey
{
    public int Id { get; set; }

    public string CacheKey => $"{GetType().Name}:{Id}";
}

Next, we need to create the MediatR behavior that will take care of caching. This uses IMemoryCache provided in ASP.NET Core solely because I don't know the definition of your ICacheClient interface:

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;
    }
}

Lastly, we need to register the behavior with Autofac:

builder
    .RegisterGeneric(typeof(CacheBehavior<,>))
    .As(typeof(IPipelineBehavior<,>))
    .InstancePerDependency();

And there we have it, caching is now a cross-cutting concern, which implementation lives in a single class, making it easily changeable and testable.

We could apply the same pattern for different things and make the handlers only responsible for business logic.

Mickaël Derriey
  • 12,796
  • 1
  • 53
  • 57
  • I completely agree, things like caching and logging should be done using behaviors. – Peter Bons Dec 29 '18 at 09:45
  • I am already using behaviors for general logging but the logging I was going to do in the command is weather the object was retrieved from cache or db. Your answer was perfect in every way as I can now actually apply behaviors to caching (didnt even think about it). In the example given. Given the example you gave me, how can i create communication between logging behavior and caching so that if the caching didnt find it I tell the logger to make a log. – Zoinky Dec 29 '18 at 14:38
  • second question, I have mine defined as `builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>));` any different than how your registering your behaviours? `builder .RegisterGeneric(typeof(CacheBehavior<,>)) .As(typeof(IPipelineBehavior<,>)) .InstancePerDependency();` – Zoinky Dec 29 '18 at 14:41
  • They're equivalent as `InstancePerDependency` is the default lifetime scope applied by Autofac. It means that a new instance will be created each time it's resolved from the container. – Mickaël Derriey Dec 29 '18 at 20:21
  • To answer the second part of your question, if the caching behavior needs to do logging, then your caching interface needs to be injected in the behavior. – Mickaël Derriey Dec 29 '18 at 20:23
  • Wouldn't that break SOLID principal since caching shouldn't do logging? One more question :), The cache behavior is great but now let say a certain coin gets updated through UpdateCoinCommand, what would be the most appropriate way to invalidate the cache for that particular coin, do it in the command itself goes back to injecting cache, I am thinking have a specific command that is responsible for invalidating cache objects and possibly refreshing I am trying to learn microservices to follow all best practices and want to make sure my understanding is solid, including of SOLID. – Zoinky Dec 29 '18 at 21:20
  • I don't think it would since this logging is related to caching. I would agree it we were talking about general purpose logging like _received this request/returned this response_. Cache invalidation is always a tricky subject, and unrelated to whether you implement a cache behavior. I don't think there's a one-size-fits-all answer here, you might have better luck asking a separate question if you want to discuss this more in depth. – Mickaël Derriey Dec 30 '18 at 01:37
  • @MickaëlDerriey made a separate q at https://stackoverflow.com/questions/53981235/mediatr-where-is-the-right-place-to-invalidate-update-cache – Zoinky Dec 30 '18 at 20:42