I have a couple of interfaces as follows, used to mediate query objects and query handlers:-
public interface IQuery<TResponse> { }
public interface IQueryBus
{
public TResponse Resolve<TResponse>(IQuery<TResponse> query);
}
It's supposed to be invoked in the controller layer as follows:-
FooQuery query = ... // comes from model binding
FooResponse response = this.bus.Resolve(query);
The bus has the responsibility for locating a handler for the query:-
public interface IQueryHandler<in TQuery, out TResponse>
where TQuery : IQuery<TResponse>
{
public TResponse Handle(TQuery query);
}
The handlers are wired up using a dependency resolver (I'm using Ninject, but it shouldn't matter terribly).
My current implementation is along the following lines:-
public TResponse Resolve(IQuery<TResponse> query)
{
var queryType = query.GetType();
var handlerType = typeof(IQueryHandler<,>)
.MakeGenericType(queryType, typeof(TResponse));
var handleMethod = handlerType.GetMethod("Handle", ...);
var handler = kernel.Get(handlerType);
return (TResponse)handleMethod.Invoke(handler, new[] { query });
}
My problem is that I'd like to avoid the call to MethodInfo.Invoke
for both stylistic and performance reasons.
If I change the signature of IQueryBus.Resolve
to:-
public TResponse Resolve<TQuery, TResponse>(TQuery query)
where TQuery : IQuery<TResponse>
Then it's pretty simply to change the implementation:-
var handler = kernel.GetType<IQueryHandler<TQuery, TResponse>>();
return handler.Handle(query);
but then of course the generic type parameters cannot be inferred at the point of use:-
FooResponse response = this.bus.Resolve<FooQuery, FooResponse>(query);
Can I have my cake and eat it? Is there a solution that satisfies the following:-
- Doesn't change the signature of
IQueryBus.Resolve
(i.e. allows its generic type parameters to be inferred)
- Allows for compile-time type-safety of the
Query
, the Response
, and the invocation of the Handler
(i.e. no reflection or dynamic dispatching)
- Doesn't change the interface of
IQueryHandler
(i.e. doesn't expose the handlers to the implementation constraints of the bus)
I have control over the rest of the code, I can introduce new types and entirely replace the implementation of IQueryBus
. I can cache work done by the dependency resolver on app start (which is the most promising lead I have atm).
If the worst comes to the worst, then I guess asking the caller to specify the generic type parameters isn't the end of the world, but I'm hoping for something a bit more elegant that minimises the opportunity for future mistakes.
There's a pretty similar implementation at ShortBus on github if you're looking for a more complete real-world example.