lundi 25 mars 2019

Isn't CQRS + MediatR a glorified service locator pattern?

I have started working with CQRS a while ago using ASP.NET Core and the awesome MediatR library. I love MediatR and the idea to decouple the Request/Notification objects from the actual implementations (Request/Notification handlers).

For messaging, the INotification part is perfect, because as the sender I do not care about who will receive my notification and what they will do with it.

For IRequests however, I am making an assumption that there exists a single IRequestHandler that will process my request and return the data I want. And by injecting IMediatR instead of the handler itself, I am hiding this dependency, which feels much like the Service Locator (Anti-)Pattern.

Here is a pseudo-code example to illustrate. Assume PerformComplexCalculationCommand, PerformComplexCalculationCommandHandler and CalculationResult exists.

public class ComplexController : Controller {
    private readonly IMediator _mediator;
    public ComplexController(IMediator mediator){
        _mediator = mediator;
    }

    public async Task<IActionResult> SomeAction(PerformComplexCalculationCommand command){
        // Here I have an implicit dependency on an IRequestHandler<PerformComplexCalculationCommand> which I'm hiding
        var result = await _mediator.Send<CalculationResult>(command); // generic argument made explicit for illustration
        return Ok(result);
    }
}

This is code that I learnt to write from many Clean Architecture lectures from professionals. Now let me change this up a little to illustrate my point.

public async Task<IActionResult> SomeAction(PerformComplexCalculationCommand command){
    IRequestHandler<CalculationResult> handler = _mediator.GetHandler<PerformComplexCalculationCommand, CalculationResult>();
    var result = await handler.Handle(command);
    return Ok(result);
}

This is the same logical operation, right? In fact this is exactly what MediatR does in its Send implementation.

Simplified code, I stripped out the irrelevant parts, it is from Mediator.cs:35-40

var handler = Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse)));
return handler.Handle(request, cancellationToken, _serviceFactory);

where RequestHandlerWrapperImpl will call a service locator to look up and create the actual IRequestHandler. Now you can just call IMediator IServiceContainer and you arrived at the Service Locator pattern.

So how is this not a violation of the Explicit Dependencies principle? Looking at my controller code, there is not a sign that it relies on PerformComplexCalculationCommandHandler for it to work correctly. How do I know that an IRequestHandler is safe to remove, or change. Or is my logic flawed? Don't take me wrong, I love to work with MediatR, I am just worried about reintroducing the maintenance headaches I learnt to avoid by using explicit dependency injection instead of locating or creating dependencies myself.

1 commentaire: