4

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.

Iain Galloway
  • 18,669
  • 6
  • 52
  • 73
  • not this way (see [Why can't these generic type parameters be inferred?](http://stackoverflow.com/questions/2990275/why-cant-these-generic-type-parameters-be-inferred) ) - as interface constraint is not enough to infer type (due to multiple similar interfaces)... It may be good to comment on what type of changes you are open to. – Alexei Levenkov Feb 06 '14 at 17:52
  • 1
    Edited the question to better describe the constraints. I totally understand why the compiler can't infer the types in the latter example (the compiler can't know that I don't have any types that implement IQuery for two different Responses, even though I as the designer can see that that's the entire reason the marker interface exists!) – Iain Galloway Feb 06 '14 at 18:15
  • What is possibility of IQuery with same TResult type being implemented by multiple queries? – Euphoric Feb 06 '14 at 18:47
  • Exactly 1:1 mapping between Query objects and Results. – Iain Galloway Feb 06 '14 at 18:48

3 Answers3

3

If there is no possibility to have multiple Queries using same TResult, then something like this is possible.

public interface IQueryHandler<out TResponse>
{
    TResponse Handle(object query);
}

public abstract class QueryHandler<TQuery, TResponse> : IQueryHandler<TResponse>
{
    TResponse IQueryHandler<TResponse>.Handle(object query)
    {
        return Handle((TQuery)query);
    }

    protected abstract TResponse Handle(TQuery query);
}

public TResponse Resolve<TResponse>(IQuery<TResponse> query)
{
    var handlerType = typeof(IQueryHandler<TResponse>);

    IQueryHandler<TResponse> handler = kernel.Get(handlerType);

    return handler.Handle(query);
}

You might loose typing a little in the middle, but the specific queries and handler are all properly typed just like before. And only performance impact I see is possibility of boxing when TResponse is a value type. But that would get overshadowed by IoC looking for the concrete handler.

Euphoric
  • 12,645
  • 1
  • 30
  • 44
  • Yeah, I think it's inevitable that something somewhere will have to cast! I'm not sure about the template method pattern here. It's better than what I have now (so +1!) but I'm wary of `IQueryHandler` not accurately representing the query handler's contract. Perhaps if instead of a template method, I could have another object whose job it is to do the cast? Worst-case scenario would be me needing to use `MakeGenericType` to get an instance of it from the container. – Iain Galloway Feb 07 '14 at 14:13
  • Accepting this answer, though as described in my above comment I used an adapter inside the bus rather than a template method to perform the cast - preventing perverse implementations of IQueryHandler. – Iain Galloway Feb 07 '14 at 15:35
  • @IainGalloway Can you post code? I would like to see how you did it. – Euphoric Feb 07 '14 at 15:40
  • Sure, I'll add another answer – Iain Galloway Feb 07 '14 at 16:04
1

Posted by request:-

interface IQueryHandlerAdapter<TResponse>
{
  TResponse Handle(IQuery<TResponse> query);
}

class QueryHandlerAdapter<TQuery, TResponse>
  where TQuery : class, IQuery<TResponse>
{
  private readonly IQueryHandler<TQuery, TResponse> handler;

  public QueryHandlerAdapter(IQueryHandler<TQuery, TResponse> handler)
  {
    this.handler = handler;
  }

  public TResponse Handle(IQuery<TResponse> query)
  {
    var q = query as TQuery;

    if (q == null)
    {
      throw new InvalidOperationException("...");
    }

    return this.handler.Handle(q);
  }
}

class SynchronousQueryBus
{
  private readonly IKernel kernel;

  public SynchronousQueryBus(IKernel kernel)
  {
    this.kernel = kernel;
  }

  public TResponse Resolve<TResponse>(IQuery<TResponse> query)
  {
    if (query == null)
    {
      throw new ArgumentNullException("query");
    }

    var handler = this.kernel.Get<IQueryHandlerAdapter<TResponse>>();

    return handler.Handle(query);
  }
}

Then all I need to do is wire up all the Query/Response pairs to the respective concrete adapter types at app start. I'm doing that by hand for the moment, but it's not terribly complicated to loop through types implementing IQuery and bind them like that so I'll refactor once I'm done testing.

That way the mess gets contained and whoever implements the query objects and handlers has the minimum possible opportunity to get things wrong.

Iain Galloway
  • 18,669
  • 6
  • 52
  • 73
0

Basically the same Implementation but here's ours.

public interface Query<TResult> { }
public interface QueryProcessor
{
        TResult Process<TResult>(Query<TResult> query);
}
public interface QueryHandler<in TQuery, out TResult> where TQuery : Query<TResult>
{
        TResult Handle(TQuery query);
}

//GetSomethingByIdQuery Implements : Query<Something>
//so Something will be the return value 
queryProcessor.Process(new GetSomethingByIdQuery());    

//Query Processor
public TResult Process<TResult>(Query<TResult> query)
{
       var handlerType = typeof(QueryHandler<,>)
       .MakeGenericType(query.GetType(), typeof(TResult));

   dynamic handler = _container.Resolve(handlerType);


   return handler.Handle((dynamic)query);
}

Here's the Handler

public class GetSomethingByIdQueryHandler : QueryHandler<GetSomethingByIdQuery, Something>
{   
    public Something Handle(GetSomethingByIdQuery query)
    {
       using (var session = _store.OpenSession())
      {
        return session.Load<Something >(query.Id)   
      }
    }
} 
CSharper
  • 5,420
  • 6
  • 28
  • 54