Without MediatR - Request/response, multiple handlers

Arialdo Martini — 29/08/2023 — C# MediatR

Request/response, sending the same request to multiple handlers

You want to deliver the same message to more than 1 target.

We will explore the two cases, whether you want to perform a fire-and-forget (this page), or you wish to collect a reply back from all the targets, a case covered by Multiple handlers with return value.

With MediatR

This is not supported. By design, Request/response messages are dispatched to a single handler (see MediatR wiki - Basics).

If you register more than a handler for the same request, only the last one will be dispatched the request, while the other ones will be silently discarded.

The idiomatic way to cover this case is to merge the logic of the ideal multiple handlers in a single one (see Question: Multiple parallel send with merge result).

Without MediatR

With plain OOP and FP there are plenty of options for dispatching the same call to multiple targets.

You might inject more than one handler and just cycle on them:

class Client
{
    private readonly IEnumerable<IMyHandler> _handlers;

    internal Client(IEnumerable<IMyHandler> handlers)
    {
        _handlers = handlers;
    }

    internal void DispatchToAll()
    {
        var message = new SomeRequest("my message");

        var dispatchAll = _handlers.Select(h => h.DoSomething(message));

        Task.WhenAll(dispatchAll).Wait();
    }
}

code

You could use the classical GoF Composite Pattern to transparently treat group of handlers as a single instance. The idea is to move the cycle in a separate class, and to give that class the same interface of an ordinary handler:

file class Handlers : IMyHandler
{
    private readonly IEnumerable<IMyHandler> _handlers;

    internal Handlers(IEnumerable<IMyHandler> handlers)
    {
        _handlers = handlers;
    }
    
    Task IMyHandler.DoSomething(SomeRequest request)
    {
        var dispatchAll = _handlers.Select(h => h.DoSomething(request));
        Task.WhenAll(dispatchAll).Wait();
        return Task.CompletedTask;
    }
}

The client would have no clue whether the passed handler is a collection of handlers or a single instance — and in this case, which one — making it apparent once again that the plain OOP approach does exhibits low-coupling:

class Client
{
    private readonly IMyHandler _handler;

    internal Client(IMyHandler handler)
    {
        _handler = handler;
    }

    internal async Task DispatchToAll()
    {
        var message = new SomeRequest("my message");

        await _handler.DoSomething(message);
    }
}

code

You might prefer a functional approach and use an enhanced method dispatch, such as:

internal static class CompositeExtensions
{
    internal static async Task InvokeAll<T>(this IEnumerable<T> handlers, Func<T, Task> f)
    {
        await Task.WhenAll(handlers.Select(f));
    }
}

FAQs

This can be done with notifications!

You can send the same message to multiple targets using Notifications instead of Requests!

Answer
No, that’s not equivalent. There are 2 notable differences:

  1. MediatR raises an error if there is no handler registered for a specific request. With notifications, it does not, and the dispatch silently fails.

  2. Requests can return a value, Notifications cannot.

Queries/Commands and Notifications are not equivalent.

How can you dispatch a request to multiple targets and get a response?

Answer
Very good question. This deserves a dedicated page.

References

Comments

GitHub Discussions