Castle Windsor: the best way to implement 2 levels of (nested) factories?

We have a template that we have used several times, with the result that we are implementing handlers and factories in separate Dlls. We customize the exe at runtime by specifying which DLLs are loaded and therefore which handlers are available to the application.

We do this because we have custom handling for some clients, and it also provides a lot of flexibility as we can quickly develop new handlers in isolation, and test and deploy them with confidence that we haven't even touched any other components. expression. We can also handle handlers simply by discarding one replaceable dll, we have clients with strict change management procedures and they love it.

To do this, the pattern relies on two levels of factories, concrete factories that implement specific handlers, and an overarching factory (which we call Supplier ). The provider chooses which factory handler to use to create the handler.

Question: Does Windsor contain something that would make this process easier for us?
Specifically I am looking for something that might omit Handler factory objects, this is similar to what it is supposed to do.
I have read the typed factory mechanism and methods UsingFactory

and UsingFactoryMethod

but I dont see how they would be any help here.
However, I often find the Castle Windsor documentation to be dumb so I might be missing something obvious Or is there just a better way to get 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 the second DLL we implement the 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 the third Dll, we implement the 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 the 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 really needs handlers only uses the Provider

public class MessageService
{
    public MessageService(IMessageHandlerProvider handlerProvider) {}
}

      

+3


source to share


1 answer


What you are asking is indeed possible in Windsor with typed factories; instead of allowing all factories in your provider and then looking for those that can handle the message, you can ask Windsor for the handler associated with the message type and just use it. You don't need a second level factory ( IMessageHandlerFactory

) because the handler can determine which message it will bind to.

Here is a good resource for this architecture (you are probably reading this one already) that I'll hang up very quickly.

Given your interfaces, you start by registering all of your handlers

container.Register(Classes.FromAssemblyInThisApplication()
    .BasedOn<IMessageHandler>()
    .WithServiceAllInterfaces());

      

Ok, now tell Windsor that we want a factory that will return IMessageHandler

. The good thing is that we don't actually need 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 connection between our message handlers and the string passed to the factory, Windsor returns the first registered instance IMessageHandler

it finds. To create this link, we can name each message handler after the type of message it should handle.

You can do this in various ways, but I like to create a convention where the type of the message handler tells which 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 the factory that the message type should be used as the name of the handler you want to resolve. To do this, you can use a class that inherits DefaulTypedFactoryComponentSelector

. We simply override the way we define component names and return the message type we receive:

public class MessageHandlerSelector : DefaultTypedFactoryComponentSelector
{
    protected override string GetComponentName(MethodInfo method, object[] arguments)
    {
        return arguments[0].ToString();
    }
}

      

Now we can connect this selector in the factory

container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IMessageHandlerProvider>()
     .AsFactory(c =>c.SelectedWith(new MessageHandlerSelector())));

      

Here is the complete 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 that I would like to highlight:

  • you guessed it, you don't need two factories: one is enough
  • the naming convention for message handlers is not set in stone and you may decide to use a different mechanism to cancel the convention
  • I'm not talking about releasing components, but the links provide some information on this that you should look into
  • I haven't handled a case where no handler can be found, but Castle will throw itself when it cannot resolve the handler with ComponentNotFoundException

  • Perhaps the system was more reliable if the handlers were explicit about the types of messages they were handling. For example, changing the interface to IHandlerOf<T>

    c T

    is an implementation of the message type.
+5


source







All Articles