We have a pattern we've used several times, whereby we implement handlers and factories in separate Dlls. We configure exe's at runtime saying what dlls are loaded, and therefore what handlers are available to the app.
We do this because we have custom handling for some customers, also it allows great flexibility because we can quickly develop new handlers in isolation, and test and deploy them with confidence that we haven't even touched any other parts of a running application. We can also patch handlers by simply dropping in a single replacement dll, we have customers with strict change management procedures and they adore this.
To do this the pattern relies on two levels of factories, specific factories that implement specific handlers, and an overarching factory (which we call a Provider). The Provider chooses which handler factory to use to create a handler.
The question: Does Windsor contain something that would simplify this process for us?
Specifically I'm looking for something that could omit the Handler factory objects, it feels like something it should be able to do.
I've read up on the Typed Factory Facility and the UsingFactory
& UsingFactoryMethod
methods, but I can't see how they'd be any help here.
That said I often find the Castle Windsor documentation obtuse so I could be missing something obvious
Or is there just a better way of getting the same end goal that I haven't considered.
Here's some code to illustrate, first message, handler and factory interfaces
public interface IMessage
{
string MessageType { get; }
}
public interface IMessageHandler
{
void Process(IMessage message);
}
public interface IMessageHandlerFactory
{
bool CanProcessType(string type);
IMessageHandler Create();
}
In a second DLL we implement a handler and factory for Type1
public class Type1MessageHandler
: IMessageHandler
{
public void Process(IMessage message) { }
}
public class Type1MessageHandlerFactory
: IMessageHandlerFactory
{
public bool CanProcessType(string type)
{
return type == "Type1";
}
public IMessageHandler Create()
{
return new Type1MessageHandler();
}
}
In a third Dll we implement a handler and factory for Type2
public class Type2MessageHandler
: IMessageHandler
{
public void Process(IMessage message) { }
}
public class Type2MessageHandlerFactory
: IMessageHandlerFactory
{
public bool CanProcessType(string type)
{
return type == "Type2";
}
public IMessageHandler Create()
{
return new Type2MessageHandler();
}
}
In a windows service we implement the provider
public interface IMessageHandlerProvider
{
IMessageHandler Create(string messageType);
}
public class MessageHandlerProvider
: IMessageHandlerProvider
{
IEnumerable<IMessageHandlerFactory> factories;
public MessageHandlerProvider(IWindsorContainer wc)
{
factories = wc.ResolveAll<IMessageHandlerFactory>();
}
public IMessageHandler Create(string messageType)
{
foreach (var factory in factories)
if (factory.CanProcessType(messageType))
return factory.Create();
throw new UnableToFindMessageHandlerFactoryForType(messageType);
}
}
The service that actually needs the handlers only uses the Provider
public class MessageService
{
public MessageService(IMessageHandlerProvider handlerProvider) {}
}
What you are asking is indeed possible in Windsor with typed factories; instead of resolving all the factories in your provider and then looking for the ones that can process the message, you could ask Windsor for the handler that is linked to the message type and just use it. You don't really need the second level factory (IMessageHandlerFactory
), because the handler can tell what message it will link to.
Here is a nice resource for this architecture (you've probably read this one already) which I'll summarize very quickly.
Given your interfaces, you start by registering all your handlers
container.Register(Classes.FromAssemblyInThisApplication()
.BasedOn<IMessageHandler>()
.WithServiceAllInterfaces());
Ok, now let's tell Windsor we want a factory that will return a IMessageHandler
. What is nice is that we don't actually have to code anything for the factory.
container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IMessageHandlerProvider>().AsFactory());
Now we can start using the factory
var provider = container.Resolve<IMessageHandlerProvider>();
var msg = new Type2Message();
var msgHandler = provider.Create(msg.MessageType);
The problem is that since there is no link between our message handlers and the string we pass to the factory, Windsor returns the first registered instance of a IMessageHandler
it finds. In order to create this link we can name each message handler after the message type it is supposed to handle.
You can do it in a variety of ways, but I like to create a convention where a message handler type tells what messages it can handle:
container.Register(Classes.FromAssemblyInThisApplication()
.BasedOn<IMessageHandler>()
.WithServiceAllInterfaces().Configure(c => {
c.Named(c.Implementation.Name.Replace("MessageHandler", string.Empty));
}));
Now you need to tell your factory that the message type must be used as the name of the handler you want to resolve. To do that, it is possible to use a class inheriting the DefaulTypedFactoryComponentSelector
. We just override the way component names are determined and return the message type we are receiving:
public class MessageHandlerSelector : DefaultTypedFactoryComponentSelector
{
protected override string GetComponentName(MethodInfo method, object[] arguments)
{
return arguments[0].ToString();
}
}
Now we can plug this selector in the factory
container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IMessageHandlerProvider>()
.AsFactory(c =>c.SelectedWith(new MessageHandlerSelector())));
Here is the full code to handle any messages:
var container = new WindsorContainer();
container.Register(Classes.FromAssemblyInThisApplication()
.BasedOn<IMessageHandler>()
.WithServiceAllInterfaces().Configure(c => {
c.Named(c.Implementation.Name.Replace("MessageHandler", string.Empty));
}));
container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IMessageHandlerProvider>().AsFactory(c =>c.SelectedWith(new MessageHandlerSelector())));
var provider = container.Resolve<IMessageHandlerProvider>();
var msg = new Type2Message();
var msgHandler = provider.Create(msg.MessageType);
msgHandler.Process(msg);
Here are some points I would like to underline:
ComponentNotFoundException
IHandlerOf<T>
with T
being a message type implementation.