Proposed container API change, opinions wanted

Feb 22, 2008 at 8:49 PM
I'm currently looking at implementing more general purpose lifetime management; enough people have asked about it or complained about the lack of it.

Currently, I'm thinking that we could replace both SetSingleton and RegisterInstance with this method:

interface IUnityContainer
{
IUnityContainer SetLifetime(Type t, string name, ILifetimeManager lifetime);
}

Where we would supply the following lifetime managers:

TransientLifetimeManager: Always create a new one; this is the default unless otherwise specified
SingletonLifetimeManager: Per-container singleton, what SetSingleton does now
SpecificInstanceLifetimeManager: Stores and returns a specific instance (and optionally does a weak reference to the instance), what RegisterInstance does now.

The interface for the lifetime manager looks like this:

interface ILifetimeManager : ILifetimePolicy
{
// Called when the lifetime manager is added to the container
void Initialize(ExtensionContext context);
}

ILifetimePolicy is defined in OB, and looks like this:

interface ILifetimePolicy : IBuilderPolicy
{
// Retrieve the item from backing store, or null if not there
object GetValue(IBuilderContext context);

// Store the given object into the backing store
void SetValue(IBuilderContext context, object value);

// Remove the given object from backing store
void RemoveValue(IBuilderContext context, object value);
}

I'd like to know what you folks think of this interface for lifetime management, and to the proposed change in the container. Should we remove lifetime management completely to an extension? Is this as usable as the simple SetSingleton/RegisterInstance methods? And are there any lifetimes that you can't implement using this API?

Thanks,

-Chris
Feb 23, 2008 at 4:07 PM
Edited Feb 23, 2008 at 4:09 PM
As you know, I think it is a great idea to abstract lifetime management so there isn`t only the Singleton model that is available.

With what you`re proposing, I have a few questions/comments:

  • Are you planning on encapsulating the ILocator into the ILifetimeManager?
    • Otherwise, why would need to have GetValue(context) and SetValue(context, value) on ILifetimeManager?
    • Before, ILifetimeContainer was uniquely there to root the created instances.
    • There was no notion of a builder context nor a buildKey.
    • Responsability wise, isn't it the Locator's job to find existing instances?
    • So I say GetValue shouldn't exist and SetValue shouldn't know about the context. I see both ILocator and ILifetimeManager acting in pair, not the latter facading the former.

  • Here again, RegisterInstance was meant to publish external/existing instances into the container. So I don't see the need for SpecificInstanceLifetimeManager.
    • However, I think RegisterInstance should optionally accept an ILifetimeManager as its last parameter instead of a "containerControlsLifetime" boolean that forced it to being singleton.

  • I'd rename SingletonLifeTimeManager to something that expresses the fact that it is not a real singleton but local to the container as your comment describes. PerContainerLifetimeManager sounds good to me.
Feb 23, 2008 at 5:45 PM
Using the locator won't work in general. The problem is there's one locator per container. So what happens in multi-threaded situations? Thread A sticks it's per thread object into the locator. Thread B looks in the locator, finds an object, and boom! You've got the wrong one.

The locator and lifetime container are available to lifetime managers if they want it, but in general each one will need to implement some sort of backing store, thus the GetValue method.

RegisterInstance - the containerControlsLifetime didn't force singleton, the mere act of registering an instance did. The containerControlsLifetime flag simply made the container hold onto a strong reference to the instance. RegisterInstance (and SpecificInstanceLifetimeManager) says "If somebody asks for this type, give them this object". It sounded like a lifetime to me.

Feb 23, 2008 at 6:17 PM
I just did a quick survey of the code, and it turns out that the locator is passed around a lot, but isn't actually used very often. It's used by the DependencyResolver, the SingletonStrategy, and the LookupParameter. None of those things are critical, and in the current Unity codebase anyway LookupParameter isn't used at all, and with this proposal SingletonStrategy is replaced by LifetimeStrategy which uses the LifetimePolicy. The DependencyResolver could easily not check the locator.

So I'm beginning to wonder if the Locator has outlived its usefulness?
Feb 23, 2008 at 6:41 PM
Ok I think I see where you're heading.

However, I'm not sure I can clearly identify the responsabilities of each interface.

  • What will be the entry point to get access to an instance?
  • Will it still be the ILocator?
  • Will Locator have access to the IPolicyList, fetch the ILifetimeManager policy given a key and delegate Add, Remove and Contains calls to GetValue, SetValue and RemoveValue?
    • That implies that the Locator's WeakRef Dictionary would be removed and replaced with the ILifetimeManager that acts as a backing store.
  • Is it worth keeping ILifetimeContainer ?
Feb 23, 2008 at 9:26 PM

  1. What will be the entry point to get access to an instance?
  2. Will it still be the ILocator?


It would be the ILifetimePolicy. The locator could quite possibly go away. Appropriate lifetime policies would replace everything the locator does with a more flexible approach.


  1. Will Locator have access to the IPolicyList, fetch the ILifetimeManager policy given a key and delegate Add, Remove and Contains calls to GetValue, SetValue and RemoveValue?

* That implies that the Locator's WeakRef Dictionary would be removed and replaced with the ILifetimeManager that acts as a backing store


I think if we keep Locator at all, it's only purpose would be to be that weak reference dictionary that various Lifetime policies could stick stuff in if they wanted.


Is it worth keeping ILifetimeContainer ?


I think so, but perhaps it doesn't need to be directly on the context anymore. It would be used by container-scoped lifetimes to do what it does now - dispose objects that need it when the container goes away.
Feb 23, 2008 at 9:42 PM
If locator goes away, who will be responsible to chain "containers" together so that when you look for a service instance registered in a parent container and look for it in a child, it will walk the parent chain in order to find it?

Different life time managers shouldn't have to deal with hierarchies as implementation will be the same for all of them, it should only care about being a backing store. That's why I thought Locator would still have its place and only use life time managers as a store.
Feb 24, 2008 at 12:48 AM

francois_tanguay wrote:
If locator goes away, who will be responsible to chain "containers" together so that when you look for a service instance registered in a parent container and look for it in a child, it will walk the parent chain in order to find it?

Different life time managers shouldn't have to deal with hierarchies as implementation will be the same for all of them, it should only care about being a backing store. That's why I thought Locator would still have its place and only use life time managers as a store.


I anticipate that each instance that needs one will have its own Lifetime manager (indexed by build key as usual). Don't forget that lifetime managers are builder policies, so the act of looking up the lifetime manager will take care of the hierarchy part automatically.
Feb 24, 2008 at 1:17 PM

ctavares wrote:
I'm currently looking at implementing more general purpose lifetime management; enough people have asked about it or complained about the lack of it.

Currently, I'm thinking that we could replace both SetSingleton and RegisterInstance with this method:

interface IUnityContainer
{
IUnityContainer SetLifetime(Type t, string name, ILifetimeManager lifetime);
}

...

I'd like to know what you folks think of this interface for lifetime management, and to the proposed change in the container. Should we remove lifetime management completely to an extension? Is this as usable as the simple SetSingleton/RegisterInstance methods? And are there any lifetimes that you can't implement using this API?

Thanks,

-Chris




I like the general suggestion of using an ILifetimeManager to provide a more consistent approach. Concerning whether this should be altogether handled through extensions, this would be my recommendation.

For both issues, I see the question coming down to convenience vs. SoC and consistency. Just as I don't believe singleton instances should be handled in a different way than sliding-cached instances, I also don't believe that lifetime management in general should be handled differently than other pre-creation/creation concerns.

As far as usability is concerned, if a LifetimeManagementExtension is registered by default then all we are really talking about is one extra method in the fluent chain each time the user needs to configure the lifetime of an object.

 
container.SetLifetime(...)
 
vs.
 
container.Configure<Lifetime>().SetLifetime(...)
 


That said, I think either way is an improvement over the existing SetSingleton() approach.

Derek
Feb 24, 2008 at 9:53 PM

derekgreer wrote:

I like the general suggestion of using an ILifetimeManager to provide a more consistent approach. Concerning whether this should be altogether handled through extensions, this would be my recommendation.

For both issues, I see the question coming down to convenience vs. SoC and consistency. Just as I don't believe singleton instances should be handled in a different way than sliding-cached instances, I also don't believe that lifetime management in general should be handled differently than other pre-creation/creation concerns.

As far as usability is concerned, if a LifetimeManagementExtension is registered by default then all we are really talking about is one extra method in the fluent chain each time the user needs to configure the lifetime of an object.

 
container.SetLifetime(...)
 
vs.
 
container.Configure<Lifetime>().SetLifetime(...)
 


That said, I think either way is an improvement over the existing SetSingleton() approach.

Derek


I see where you're coming from, but my recent thinking is leaning towards having them all. Here's why: they're more convenient, and less confusing.

As far as separations of concerns goes, I understand the, well, concerns ;-), but I'm not sure they apply. Looking at what UnityContainer actually is, it's a facade over ObjectBuilder. Actually, looking at the code, UnityContainer doesn't actually DO anything but hold onto the objects that OB needs and fire events. Looking at the GoF book:


Use the Facade pattern when

  • you want to provide a simple interface to a complex subsystem. Subsystems often get more complex as they evolve. Most patterns, when applied, result in more and smaller classes. This makes the subsystem more reusable and easier to customize, but it also becomes harder to use for clients that don't need to customize it. A facade can provide a simple default view of the subsystem that is good enough for most clients. Only clients needing more customizability will need to look beyond the facade.

  • there are many dependencies between clients and the implementation classes of an abstraction. Introduce a facade to decouple the subsystem from clients and other subsystems, thereby promoting subsystem independence and portability.

  • you want to layer your subsystems. Use a facade to define an entry point to each subsystem level. If subsystems are dependent, then you can simplify the dependencies between them by making them communicate with each other solely through their facades.



When building a Facade, convenience is more important than underlying structure, since the underlying subsystem (ObjectBuilder, in this case) does have good Separation of Concerns under the hood (at least, I hope it does). Since one of our goals is to introduce IoC to the non-Alt.NET elite, having a simpler to use front end seems to me to be a better approach.

Thoughts?

-Chris
Feb 24, 2008 at 11:37 PM


ctavares wrote:

I see where you're coming from, but my recent thinking is leaning towards having them all. Here's why: they're more convenient, and less confusing.



I'm not sure I follow. Do you mean to say that you intend to include both a SetSingleton() and a SetLifetime()?



ctavares wrote:

As far as separations of concerns goes, I understand the, well, concerns ;-), but I'm not sure they apply. Looking at what UnityContainer actually is, it's a facade over ObjectBuilder.



I agree that Unity is a facade, but I still think that it should take a consistent approach to how it exposes the capabilities of Object Builder. For this particular issue, I don't think the issues of usability and consistency are at odds.

As a supporter of the P&P team, and as an architect who plans to incorporate Unity into my team's project :) , I would personally rather see all configuration such as this done through the use of Extensions. I don't see the extra step of using the Configure method as being significantly less usable, so my vote here would be for consistency to win out.

Additionally, when we are talking about usability there is something to be said for consistency. Consistency leads to assumptions. The more assumptions that can be made about an API, the more usable it becomes. Given that lifetime management is just another type of variability applied to the build up/resolution life cycle of an object, accomplishing this using the same mechanism you would accomplish other forms of variability in a way would increase usability.

Derek
Feb 25, 2008 at 1:49 PM
@Chris:

Thanks for pointing out about being a policy, hence supporting hierarchies; I totally missed this one! It makes a lot more sense to me now!

@Derek:

I agree with Chris that being a facade, it has to offer some simplification over OB. If all services were to be offered through Configure, then arguably you'd prefer to see Register<TFrom, To> and Get<T>() as methods part of their underlying extensions (DefaultBehaviors and Strategies extensions).

All the container offer is holding references to the different OB objects (PolicyList, Strategy Chain, ...) and a facade for type mapping and buildKey creation and now life time management. So I think the interface is pretty cohesive.

If they were to target 3.5, i'd offer those wrappers through extension methods but they're not.
Feb 25, 2008 at 2:58 PM
While I agree that Unity is a facade in pattern, it is a container in function. As a container, it would be expected to provide basic register and get functionality. I'm certainly not arguing the interface should be completely reduced down to Configure<T>(). I'm only suggesting that lifetime management in particular falls into the same category as other variant behaviors that occur beyond the basic registration and retrieval of objects.


Derek