lundi 2 février 2015

Generic queries and handlers without reflection

I'm trying to implement generic queries and handlers so that I can make requests with simple syntax, like this:



var query = new HelloQuery("hi");
var result = processor.Process(query);


This post explains a brilliant technique for accomplishing this, but it uses reflection and dynamic to resolve handlers for queries. I came up with an alternative using generics.


The central idea is the IQuery:



public interface IQuery<TSelf, TResult>
where TSelf : IQuery<TSelf, TResult>
{
}


One such implementation could be:



public class HelloQuery : IQuery<HelloQuery, string>
{
public HelloQuery(string message)
{
this.message = message;
}

private readonly string message;
public string Message
{
get { return this.message; }
}
}


The TSelf type argument is the key difference between my technique and the other post's technique. It allows us to avoid reflection in the HandlerResolver:



public interface IHandlerResolver
{
IQueryHandler<TQuery, TResponse> GetHandlerFor<TQuery, TResult>()
where TQuery: IQuery<TQuery, TResult>;
}

public class HandlerResolver : IHandlerResolver
{
private readonly IContainer container;

public HandlerResolver(IContainer container)
{
this.container = container;
}

public IQueryHandler<TQuery, TResult> GetHandlerFor<TQuery, TResult>()
where TQuery : IQuery<TQuery, TResult>
{
var handler = this.container.Get<IQueryHandler<TQuery, TResult>>();
return handler;
}
}


You would register handlers at the composition root. Here's the interface for a handler, and an implementation for HelloQuery:



public interface IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TQuery, TResult>
{
TResult Handle(TQuery request);
}

public class HelloQueryHandler : IQueryHandler<HelloQuery, string>
{
public string Handle(HelloQuery request)
{
var response = string.Format("Well \"{0}\" yourself!", request.Message);
return response;
}
}


And finally, the actual query processor:



public interface IQueryProcessor
{
TResult Process<TQuery, TResult>(IQuery<TQuery, TResult> request)
where TQuery : IQuery<TQuery, TResult>;
}

public class QueryProcessor : IQueryProcessor
{
private readonly IHandlerResolver handlerResolver;

public QueryProcessor(IHandlerResolver handlerResolver)
{
this.handlerResolver = handlerResolver;
}

public TResult Process<TQuery, TResult>(IQuery<TQuery, TResult> request)
where TQuery : IQuery<TQuery, TResult>
{
var handler = this.handlerResolver.GetHandlerFor<TQuery, TResult>();
var response = handler.Handle((TQuery)request);
return response;
}
}


The interfaces would be simpler without the queries' TSelf type argument, but then the C# compiler can't infer the types, and you end up having to spell out the full Process() call like processor.Process<HelloQuery, string>(query). I'm trying to avoid this ugliness.


My question is: Is the TSelf type argument a code smell? Is there a better alternative that doesn't require falling back on reflection?






Aucun commentaire:

Enregistrer un commentaire