Ayende @ Rahien

Unnatural acts on source code

Windsor, Decorators and Chains of Responsabilityes, Oh YEAH!

A few days ago I made a small change to Windsor, the change was basically stopping Windsor from trying to resolve a dependency using a dependency that it is already resolving. Now, this is prettry hard to explain without a good example, so consider this one:

image

Let us say that we have the standard Chain Of Responsability here, each finder will check its store for an item that matches the specification, and would call the next one in the chain if it can't. Now, previously you had to instruct Windsor explicitly about who was the next in the chain, something like this:

<components>
	<component id="cache_finder" service="IResultFinder`1"
		type="CacheResultFinder`1">
		<parameters>
			<finder>${db_finder}</finder>
		</parameters>
	</component>
	<component id="db_finder" service="IResultFinder`1"
		type="DatabaseResultFinder`1">
		<parameters>
			<finder>${ws_finder}</finder>
		</parameters>
	</component>
	<component id="ws_finder" service="IResultFinder`1"
		type="WebServiceResultFinder`1">
		<parameters>
			<finder>${failed_finder}</finder>
		</parameters>
	</component>
	<component id="failed_finder" service="IResultFinder`1"
		type="FailedResultFinder`1"/>
</components>

That is quite a bit of XML, and while it works, it make it awkward to understand what is going on in complex scenarios. The main issue here was that Windsor looked at CacheResultFinder<T>, saw that it accepted IResultFinder<T> and that it had a valid component that had this service (CacheResultFinder<T>) and promptly threw a dependency cycle exception. As a direct result of that, we had to manually override Windsor's selection, and also had to have a FailedResultFinder<T>, which must go on the end of the chain, again, because otherwise Windsor would try to resolve the ctor(IResultFinder<T> finder) constructor.

This is a workable solution, but I feel that this is telling the container way too much, why isn't it smart enough to figure it out? The problem with saying it about OSS software is that I can't really rant about it, since the answer is usually "we accept patches", bummer. So, I went ahead and implement this feature in Windsor, where it will not try to resolve what is currently being resolve. The end result is that for the scenario above we can give up on FailedResultFinder<T> and write just this:

  <components>
    <component id="cache_finder" service="IResultFinder`1"
      type="CacheResultFinder`1"/>
    <component id="db_finder" service="IResultFinder`1"
      type="DatabaseResultFinder`1"/>
    <component id="ws_finder" service="IResultFinder`1"
      type="WebServiceResultFinder`1"/>
  </components>

We put the responsability for putting the chain of responsability together in the hand of the container, and it will build the system using simple first come fist on the chain approach, until it runs out of components that can satisfy the requirement, in which case it will use the default constructor.

Now this is much better, no?

Comments

Alex Henderson
06/11/2007 07:43 AM by
Alex Henderson

Love it, that's so much easier to work with then the previous approach :) is it in the trunk already?

Mark Monster
06/11/2007 07:58 AM by
Mark Monster

Hmm, sounds nice. But at the end, normally there is no ordering in XML-Elements is there? So the ordering is implicit. I think this will work, but maybe these chains should be grouped in a way that the chains can be found easyly.

Ayende Rahien
06/11/2007 08:46 AM by
Ayende Rahien

@Mark,

XML has strict ordering.

I don't follow the rest.

Mark Monster
06/11/2007 09:07 AM by
Mark Monster

Hmm just to add. I like to document my solution. If the code/xml doesn't speak for itself I tend to add comments. What I do in the case of a responsability chain is.

<component.../>

I'm not sure if there can be found a better way to document the chain with name etc... I was thinking about something like this...

<component.../>

But now mentioning this, I don't think this solution is desirable.

Simone Busoli
06/11/2007 09:10 AM by
Simone Busoli

That's interesting, Ayende. Could you explain a little better what happened with the former approach? You say that a dependency cycle exception was thrown, was it handled by Windsor or what?

Damien Guard
06/11/2007 09:36 AM by
Damien Guard

http://www.answers.com/responsibility&r=67

Ayende Rahien
06/11/2007 11:01 AM by
Ayende Rahien

Simone,

It would throw, unless you would instruct it to seek a specific one, that is why you have all the ${db_finder} elements.

Nate Kohari
06/11/2007 11:41 AM by
Nate Kohari

I would agree that while this is a pretty clean answer, it seems like there should be more explicit indication that it's a chain -- either:

<component .../>

<component .../>

Or:

<component chain="A" .../>

<component chain="A" .../>

Otherwise, if your schema gets very complex, it might be difficult to discern which components are being resolved. For example, if you have:

// ...another 50 component definitions...

Wouldn't the second "chained" request for IService result in SerivceImpl2? This seems like it could be counter-intuitive...

Ayende Rahien
06/11/2007 12:09 PM by
Ayende Rahien

@Nate,

If you got to this point, you really should split the configuration to separate files, and you can always override the default behavior

Bill Poole
06/11/2007 12:18 PM by
Bill Poole

Great work! This is exactly what I was looking for!

Eric Nicholson
06/11/2007 01:08 PM by
Eric Nicholson

I've actually run into this exact problem in the past. The work around I used was to make the constructor for each of the components in the chain take a simple Object, rather than the Interface type, and then do a cast.

This solution looks great! I would prefer something a little more explicit though in case you needed to select a specific service implementation later. Maybe the syntax mentioned earlier. Or even, just use the first XML. In my experience Windsor would through a cycle dependency exception in that situation as well.

Great stuff!

Simone Busoli
06/11/2007 04:41 PM by
Simone Busoli

As other observed I think that transparently providing this behavior might be misleading. I'd opt for a more explicit syntax like the one suggested by Nate.

BTW, do you know how other IoC containers deal with this?

Ayende Rahien
06/11/2007 06:31 PM by
Ayende Rahien

@Simone,

This is optional, but I like the syntax. Nate's second suggestion is possible today.

About other IoC, no idea, frankly.

Comments have been closed on this topic.