Skip to content

Proposal: Mediator 3.0 design to support multiple configuration and fast switching. #128

@TimothyMakkison

Description

@TimothyMakkison

I believe it's possible to merge the two design together in a way which would be fast, configurable and while supporting .NET Standard. See #98

Configuration

Add a property to AddMediator that accepts an array of types. At compile time the array is examined and all relevant Mediator types are added from their respective projects. This is used to create a unqiue implementation of IMediator used for this AddMediator call. We can use interceptors or psuedo-interceptors (using caller attributes) to return the respective Mediator class. This way we can have multiple configurable instaces of IMediator in one project.

See #125

services.AddMediator(options =>
{
    options.Namespace = "SimpleConsole.Mediator";
    options.Assemblies = [typeof(DogModels.Doggy), typeof(DogHandlers.DogHandler)],
});

Using a pseudo-intercetor (interceptor could be used instead)

public static void AddMediator(.... parameters go here, [CallerLineNumber] line = 0, [CallerFilePath] file = "")
{
    // an efficient condition can be built using the line number, file path length and varying chars in the file path
    // not sure how much the compiler will do
    return (line, path) switch
    {
        (0, _) => Mediator1.AddMediator(services),
        (42, { Length: 15 }) => Mediator2.AddMediator(services),
        (42, { Length: 21 }) => Mediator3.AddMediator(services),
        _ => throw new ArgumentOutOfRangeException()
    }
}

There are a variety of different configuration approaches, I chose this one for its simplicity to demonstrate the concept.

Fast type switching

Iirc one of plans for Mediator 3.0 attempted to resolve #7, by analyzing all calls to ValueTask<TResponse> Send<TResponse> and intercepting it with the correct method. Unfortunately this wouldn't be compatible with multiple projects per class as different type could be registered making it impossible to be sure that the relevant method exists on the IMediator implementation and then calling it.

Instead we could use a pseudo-interceptor to generate switch statements based upon known concrete type calls for each Mediator implementation. As each Mediator implementation has different types registered it would be neccessary to emit an exception when an invalid IRequest object is passed into Send.
See #97

// Generated for the Mediator class with types from DogModel and DogHandler registered
public ValueTask<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default, [CallerLineNumber] line = 0, [CallerFilePath] file = "")
{
    switch(line, path)
    {
        case (0, _): return Send((DogRequest)request, token); break;
        case (42, { Length: 15 }): return Send((BigDogRequest)request, token); break;
        case (42, { Length: 21 }): throw new Exception($"This should not happen, unregistered request {request.GetType()}"), // Concrete type not registed to this class
        default: return Send(request, token);
    }

    return default;
}
// Generated for the Mediator class with types from CatModel and CatHandler registered
public ValueTask<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default, [CallerLineNumber] line = 0, [CallerFilePath] file = "")
{
    switch(line, path)
    {
        case (0, _): throw new Exception($"This should not happen, unregistered request {request.GetType()}"), // Concrete type not registed to this class
        case (42, { Length: 15 }):throw new Exception($"This should not happen, unregistered request {request.GetType()}"), // Concrete type not registed to this class
        case (42, { Length: 21 }): return Send((CatRequest)request, token); break;
        default: return Send(request, token);
    }

    return default;
}

It's possible to achieve similar results using an interceptor by checking the type of IMediator to see if it supports the conrete IRequest object.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions