Whilst I am a massive fan of MediatR, it feels strange to take it as a dependency on all my projects (even those that don't "dispatch" and are merely posting POCOs reference the marker interfaces).
If one were to write a similar mediator library that did other things but had common Send/Publish functions, what is the need therefore for marker interfaces when the below code works with plain POCOs?
The code delineates between a request/notification via the Send/Publish functions, so are these marker interfaces redundant?
If I were to implement my own mediator, are there any issues with removing the marker interfaces from Notification
and Request
?
It is more about design principle of marker interfaces as opposed to wanted to change MediatR itself (to avoid offence I have removed the MediatR, the lib i repeat for the record is awesome and i am adapting it for my own purposes with multithreaded synchronized background stream-based dispatch hence the design questions).
Handler interfaces
public interface INotificationHandler<in TNotification> where TNotification : class
{
Task Handle(TNotification notification, CancellationToken cancellationToken);
}
public interface IRequestHandler<in TRequest, TResponse> where TRequest : class
{
Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}
Plain POCO without marker interfaces
class TestNotification
{
public int UserId { get; set; }
public int Age { get; set; }
}
class TestRequest
{
public int UserId { get; set; }
}
Handlers
class TestRequestHandler : IRequestHandler<TestRequest, int>
{
public Task<int> Handle(TestRequest request, CancellationToken cancellationToken)
{
return Task.FromResult(request.UserId);
}
}
class TestNotificationHandler : INotificationHandler<TestNotification>
{
public Task Handle(TestNotification notification, CancellationToken cancellationToken)
{
Console.WriteLine("hello");
return Task.CompletedTask;
}
}
Main
static void Main(string[] args)
{
_ = new TestNotificationHandler()
.Handle(new TestNotification(), CancellationToken.None);
var result = new TestRequestHandler()
.Handle(new TestRequest() { UserId = 111 }, CancellationToken.None)
.Result;
Console.WriteLine($"result is {result}");
}
C# fiddle
You're right about INotification
. If you were to write your own implementation of MediatR, you could basically remove that marker interface.
For IRequest
, however, you need the response type in your request definition in order to be able to handle the response after calling the mediator.
Consider a request without a defined response type:
public class TestRequest
{
}
Now, if you would call the mediator, there would be no way to determine the response type:
IMediator mediator = GetMediator();
// There is no way to type the response here.
var response = mediator.Send(new TestRequest());
The only way mediator is able to type the response, is because the response definition is in the request definition:
public interface IRequest<out TResponse> : IBaseRequest { }
And the IMediator
Send
is defined like so:
Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);
That makes it possible to know the return type.
So, long story short:
INotification
IRequest<TResponse>