Still plugging away on the IoC container...
This post was published on an earlier personal blog, no longer online. ‘Caraway’ grew up and became Autofac, still an active and evolving open-source project. The project is introduced in an earlier post, and you can watch the design start to evolve in the previous post.
No one has seen an update for a while but Carway is moving along. I hope that the result will be more than “just another IoC container” :)
One of the reasons that Caraway is tricky to develop is that it attempts to tackle the problem of component lifecycle at a level more fundamental than I am aware of in other containers.
Giving the container control over component creation places a great responsibility on the container: disposing of each component when it is no longer required.
If component A
is provided by the container, then the user of that component - that called Resolve()
to obtain a reference to it, doesn’t know whether that component was created specifically for its use, or if that instance is shared with others as in the case of a singleton. The calling code in this case generally has to ‘cheat’ and dispose of the component depending on the ownership semantics of the potential providers. Many containers provide a Release()
method, which is a converse to Resolve()
for this purpose, but that only goes so far:
This problem is that when component A
references a second component, B
, A
cannot know the disposal semantics of B
without violating the separation of interface and implementation that IoC enshrines. The container’s release policy doesn’t help, because in an IoC applicaiton, A
shouldn’t have access to the container on which it could call Release()
with its B
instance.
A general solution, employed in all of the applications I have seen so far, is for the application to clean up resources at the end of a unit of work through mechanisms like the end of a transaction or web request. If the application knows, for example, that the unit of work is likely to have created a session on a remote machine, then it can check for the connection and close it if necessary.
Doing lifecycle management by hand like this is ugly and seems like a big drawback when using IoC in general. Caraway takes a novel approach to the problem using nested containers that manage all of the non-singleton instances created within a particular unit of work. All registration is done through an ‘outer’ container:
// A -> B -> C
var builder = new ContainerBuilder();
builder.Register<A>().WithScope(InstanceScope.Factory);
builder.Register<B>().WithScope(InstanceScope.Container);
builder.Register<C>(); // Singleton
var container = builder.Build();
Requests for A
will always result in a new instance. Requests for B
within the same unit of work will result in the same instance. We can imagine B
being a resource that should be disposed.
using (var inner = container.CreateInnerContainer())
{
a = inner.Resolve<A>();
}
The inner container would generally belong to a transaction or web request. When the inner container is disposed, the instances of A and B
will be disposed, but the instance of C
will remain and can be shared between other units of work.
(Aside: has anyone encountered this pattern elsewhere in wide IoC world? Would love to know what kinds of alternatives people are using…)
Caraway has to do some bookkeeping to make sure that the singleton instance C
is unable to resolve dependencies using the transient instances of B
, that it will outlive. If C
required an instance of B
, then even if the creation of C
occurred because of a resolve call in an inner context, both C
and the instance of B
that it referenced would need to belong to and be disposed by the outer context.
This is all pretty tricky stuff to keep robust while providing all the other features of a cracking IoC container. The advanced factory support that is in the works will especially push this to the limit, but as I’ll explain sometime soon, that will be the last piece of the puzzle and the ‘grand plan’ driving Caraway will be revealed, hopefully through some good documentation and examples :)
By the way, I’m considering moving the project to Google Code, under this name or another. Codeplex is out because of the lack of first-class Subversion support, and Tigris seems to be going out of favour. What are people’s impressions of Google Code? Any other suggestions?
Comments
Posted by Luke Marshall | November 09, 2007 17:24 |
Very Smooth
I’m loving the syntax of the builder - the WithScope method is very intuitive.
In regards to usage patterns, are there any issues with keeping the result from
CreateInnerContainer()
alive for the duration of a form? i.e not just within ausing
block?I can see benefits for both patterns - but you know I love the
using
block.Haven’t developed with Google Code, but it seems like a decent alternative - let us know how it goes!
Posted by Nicholas Blumhardt | November 09, 2007 23:26 |
Thanks!
The scope of an ‘inner container’ is totally flexible - you can use one per-thread, per-form, per-HTTP request…
The important things are not to mix objects from different containers (in case one gets disposed while the other still references it) and not to dispose of the ‘outer’ container while the ‘inner’ containers are still alive.