2
\$\begingroup\$

I am trying to build a generic query handler using the MediatR (v8) library. Let's jump to the code: First of all I have an abstract query class like this:

public abstract class Query<TQueryResult> : IRequest<TQueryResult>
{
    public Guid Id { get; } = Guid.NewGuid();

    public DateTime Timestamp { get; }

    protected Query()
    {
        Timestamp = DateTime.Now;
    }
}

From the corresponding query handler I would like to return a Result wrapper object, which looks as the following:

public class Result<T> 
{
    public T Payload { get;  }
    public string FailureReason { get;  }

    public bool IsSuccess => FailureReason == null;

    public Result(T payload)
    {
        Payload = payload;
    }

    public Result(string failureReason)
    {
        FailureReason = failureReason;
    }

    public static Result<T> Success(T payload)
        => new Result<T>(payload);

    public static Result<T> Failure(string reason)
        => new Result<T>(reason);

    public static implicit operator bool(Result<T> result) => result.IsSuccess;

}

And last but not least, lets see the query handler:

public abstract class AbstractQueryHandler<TQuery, TQueryResult, TResultValue> : IRequestHandler<TQuery, TQueryResult>
    where TQuery : Query<TQueryResult>
    where TQueryResult : class
{

    public Task<TQueryResult> Handle(TQuery request, CancellationToken cancellationToken)
    {
        try
        {
            return HandleQuery(request);
        }
        catch (Exception e)
        {
            return Task.FromResult(Result<TResultValue>.Failure(GetFailureMessage(e)) as TQueryResult);
        }
    }

    public abstract Task<TQueryResult> HandleQuery(TQuery request);

    private static string GetFailureMessage(Exception e)
    {
        return "There was an error while executing query: \r\n" + e.Message;
    }
}

To be honest I am not pleased with this solution due to the three type parameters I have in the query handler. It might be possible to extract one from another using reflection but I think that is not the proper and right approach for this. I introduced TResultValue in order to make the catch part of the code working so use it as a generic type in my created error Result wrapper object. Let's see some corresponding tests to reveal my concerns regarding. First lets see the Query and the query handler in the production code:

public class GetAllProductsQuery : Query<Result<IEnumerable<ProductDto>>>
{
}


public class GetAllProductsQueryHandler : AbstractQueryHandler<GetAllProductsQuery, Result<IEnumerable<ProductDto>>, IEnumerable<ProductDto>>
{
    private readonly IProductEntityDao _productEntityDao;

    public GetAllProductsQueryHandler(IProductEntityDao productEntityDao)
    {
        _productEntityDao = productEntityDao;
    }

    public override async Task<Result<IEnumerable<ProductDto>>> HandleQuery(GetAllProductsQuery request)
    {
        var products = await _productEntityDao.GetAllAsync();

        return Result<IEnumerable<ProductDto>>.Success(products);
    }
}

And then the test:

    [Fact]
    public async Task GivenSingleProduct_thenGetAllProductsQuerySend_thenReturnsSingleProduct()
    {
        await AddToDbContextAsync(ProductFakes.ProductWithAllPropsFilled1);

        var result = await SendAsync(new GetAllProductsQuery());

        Check.That(result.IsSuccess).IsTrue();
        Check.That(result.Payload).CountIs(1);
    }

where SendAsync<T> looks as the following:

public Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
{
    var mediator = Provider.GetService<IMediator>();
    return mediator.Send(request);
}

As you can see in the GetAllProductsQueryHandler there is some kind of duplication when declaring the three types, namely <GetAllProductsQuery, Result<IEnumerable<ProductDto>>, IEnumerable<ProductDto>>. It seems really fishy to me. I also tried many other possibilities, checking articles and SO questions/answers on the internet but could not come up with a cleaner solution. I also tried using dynamic object instead of generic but I would like to have the strong type check in compiler time. So my question is: Is it possible to reduce the number of type parameters of AbstractQueryHandlerhandler to 2, resulting in a cleaner solution? Do you see any other code improvement? Thanks in advance for you help!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.