Search code examples
c#design-patternsmediator

Why INotification/IRequest marker interfaces for Mediator?


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

https://dotnetfiddle.net/HTkfh9


Solution

  • 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:

    • You don't need INotification
    • You do need IRequest<TResponse>