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 IRequest
s 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.
You are 100% correct.
RépondreSupprimerIt is an anti-pattern.