Batching WCF calls, Take 2
Posted by Davy Brion on July 1st, 2008
Update: i also have a complete series of posts on this which you may find interesting.
The first take was pretty good, after all it got Ayende-d which is the highlight of my blogging career so far
. In his post, he was pretty positive about my implementation, with one small exception:
“About the only thing that I would strive to improve there would be the need to explicitly register request & replies. I would try to get something convention based there. Maybe something like Request
Let’s give that a shot
Previously, the base implementation of the service routed the call to the correct RequestHandler like this:
public Response[] Process(params Request[] requests)
{
if (requests == null)
{
return null;
}
var responses = new List<Response>(requests.Length);
foreach (var request in requests)
{
Type handlerType = GetHandlerTypeFor(request);
if (handlerType == null)
{
responses.Add(new UnsupportedRequestResponse());
continue;
}
using (var handler = GetHandlerInstance(handlerType))
{
responses.Add(handler.Handle(request));
}
}
return responses.ToArray();
}
public virtual IRequestHandler GetHandlerInstance(Type handlerType)
{
return (IRequestHandler)Container.Resolve(handlerType);
}
public abstract Type GetHandlerTypeFor(Request request);
And then the service implementation would provide an implementation of the GetHandlerTypeFor method. In that implementation, you somehow had to map the type of the incoming Request to a handler. In the code that i posted i used a dictionary-based approach. You could’ve used a couple of if statements as well. The point is that you had to do that mapping which is indeed somewhat annoying.
First, we’ll add a new interface on top of the IRequestHandler interface:
public interface IRequestHandler : IDisposable
{
Response Handle(Request request);
}
public interface IRequestHandler<TRequest> : IRequestHandler
where TRequest : Request
{
Response Handle(TRequest request);
}
We also modify the base RequestHandler class to provide a derived generic class:
public abstract class RequestHandler : Disposable
{
protected IUnitOfWork UnitOfWork { get; private set; }
protected RequestHandler(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
protected override void DisposeObjects()
{
if (UnitOfWork != null) UnitOfWork.Dispose();
}
protected override void ClearReferences()
{
UnitOfWork = null;
}
}
public abstract class RequestHandler<TRequest> : RequestHandler, IRequestHandler<TRequest>
where TRequest : Request
{
protected RequestHandler(IUnitOfWork unitOfWork) : base(unitOfWork) {}
public abstract Response Handle(TRequest request);
public Response Handle(Request request)
{
return Handle((TRequest)request);
}
}
The only reason why i still have the base RequestHandler type is to make it easier to register the RequestHandler types with the container (which i’ll show below).
A specific RequestHandler can now be defined like this:
public class GetProductCategoriesHandler : RequestHandler<GetProductCategoriesRequest>
Right, now we can register all of our RequestHandler types with the container:
private static void RegisterRequestHandlers()
{
foreach (var type in typeof(RequestHandler).Assembly.GetTypes())
{
if (type.IsSubclassOf(typeof(RequestHandler)) && type.BaseType.IsGenericType)
{
var serviceType = typeof(IRequestHandler<>).MakeGenericType(type.BaseType.GetGenericArguments());
Register(Component.For(serviceType).ImplementedBy(type).LifeStyle.Transient);
}
}
}
So now each generic RequestHandler type is registered through the generic IRequestHandler type.
Our base Service implementation now looks like this:
public Response[] Process(params Request[] requests)
{
if (requests == null)
{
return null;
}
var responses = new List<Response>(requests.Length);
foreach (var request in requests)
{
Type handlerType = typeof(IRequestHandler<>).MakeGenericType(request.GetType());
using (var handler = (IRequestHandler)Container.Resolve(handlerType))
{
responses.Add(handler.Handle(request));
}
}
return responses.ToArray();
}
And now you no longer need to map the Request type to the RequestHandler type yourself since the container now takes care of that for us.