H2O
Creative Commons License photo credit: Flowery Luza*

I’m in the final stages of polishing Maperitive before the first release. Well, not really polishing, I have a bunch of tasks yet to do, but I think I can see the light at the end of the tunnel.

As #I stated a while back#, one of the reasons I decided to do a major rewrite of the Kosmos code was to introduce inversion of control (IoC) into the architecture. Maperitive (ex-Kosmos) now heavily relies on Windsor Castle do to the wiring. I can say that investing in IoC (and sticking to SOLIDs) now starts to pay dividends: I can add new features with more ease and they tend to affect a relatively minor part of “old” code.

The problem with using IoC containers like Windsor Castle is when the components registered exceed a certain number that you can handle in your brain (Maperitive currently has about 130 components in IoC container). Then the whole thing gets a life of its own: even if you write extensive unit tests for individual parts, it gets more and more difficult to predict how the “organism” will behave as a whole.

When used in a Windows desktop application like Maperitive, the problem gets compounded by the intricate dependencies between different lifestyles of components. This is what I want to discuss in this post.

I’ll restrict the discussion to singletons and transients, since I only use these two types in Maperitive. I’ll give examples of components in terms of a mapping application (which Maperitive is), but I think it won’t be a problem to translate this to your own areas. I’ll start with the simple case of…

Singletons Depending On Singletons

This is the easy one: singletons are components which typically share the same lifespan as the application. A Map is a good example of a singleton: the user expects the map to be available at all times (after all, what would Google Earth look like without the map?). User typically uses a mouse to move around the map. And we luckily have only one mouse on our machines, so this is an example of another singleton (I’m a bit oversimplifying the stuff here, bear with me). So the Map subscribes to the Mouse events and this can be seen as singleton to singleton dependency.

These kind of dependencies are easy to handle, since the number of created objects is typically small and they all share the same lifespan. Also, singletons are (typically) created automatically by the IoC container at the start of the application so you don’t really need to explicitly create and destroy them yourself.

Transients Depending On Singletons

This is the second common relationship in a desktop application. You have a short-term task to do (usually as a response to user’s actions) and you create a transient object to do this task. One example would be a WebClient which downloads the map data from the server: once the download is done, you can kill the client (hmmm that sounds tempting).

Transients are typically created using factories. Windsor Castle offers a cool feature called TypedFactoryFacility which makes creating factories a lot easier (you don’t need to write the actual creation code, just specify your factory interface according to facility’s conventions).

A WebClient needs to know whether it has to provide credentials for the Web proxy. We can store this information in a Configuration object, which in our case is a singleton.

A transient component states its dependency on a singleton through constructor or property injection. Again, this scenario should be easy to handle, since the transient object has a shorter lifespan than the singleton it depends upon.

Transients Depending On Transients

This is where things can get messy. There are two possible scenarios when a transient object needs another transient object:

  1. They both share the same lifespan. Let’s say our WebClient wants to check if internet connection is up before sending a request to the download server. It can use a Ping service to ping google.com (it’s immoral, I know). Since both WebClient and Ping will be used for a short time, you can inject the Ping dependency in WebClient’s constructor. When WebClient dies, Ping should also die.
  2. The lifespans overlap, but are not the same.  Example: out WebClient has detected a communication problem with the download server and sends a notice to the user. The notice is displayed as a modeless dialog, which can stay on the screen until the user clicks on the Close button. So Notice is a transient object which does not die together with its creator, WebClient: once WebClient notifies the user, it is no longer needed. Notice, however, will live on until the user chooses to close it.

In the second case, having the Notice dependency in WebClient’s constructor or property is not an ideal option, for two reasons:

  1. It is not guaranteed the notice will even be used: if the download went OK, there is no need to notify the user. If your Notice component is expensive to create, this could be an issue.
  2. By using a constructor injection for a transient component, you are effectively claiming the ownership of this component. This conflicts with the fact that the component will live on even after your main component dies.

How do you solve this problem? My suggestion is to use factories – factories are usually singleton objects, so you end up with “transient depending on singleton” case. Even better: Windsor Castle’s TypedFactoryFacility also offers a mechanism to release components created with such factory.

The last (and most problematic) case is…

Singletons Depending On Transients

The problem with this is that your supposedly transient object will live the whole duration of the application lifetime. While in some cases this could be a valid scenario, in lot of cases it’s merely an oversight of the developer (especially when confronted with a huge number of components in a system).

Windsor Castle Inspector

The solution I used in Maperitive is to write a unit test which checks for singleton-to-transient relationships in my container and fails if it finds any:

[Test]
public void SingletonsShouldNotRelyOnTransients()
{
    IWindsorContainer container = Program.CreateWindsorContainer(false);
 
    WindsorContainerInspector inspector = new WindsorContainerInspector(
        container);
    IList<KeyValuePair<ComponentModel, ComponentModel>> dependencies 
         = inspector.FindDependencies(
        (a, b) =>
            {
                return (a.LifestyleType == LifestyleType.Singleton 
                    || a.LifestyleType == LifestyleType.Undefined)
                    && b.LifestyleType == LifestyleType.Transient;
            });
 
    Assert.AreEqual(0, dependencies.Count);
}

Of course, the condition for the test failure could be a bit modified to exclude any “special cases".

The code uses the WindsorContainerInspector, a little utility class I wrote to assist in inspecting the container:

public class WindsorContainerInspector
{
    public WindsorContainerInspector(IWindsorContainer container)
    {
        this.container = container;
    }
 
    public IList<KeyValuePair<ComponentModel, ComponentModel>> FindDependencies(
        Func<ComponentModel, ComponentModel, bool> dependencyPredicate)
    {
        List<KeyValuePair<ComponentModel, ComponentModel>> dependencies 
            = new List<KeyValuePair<ComponentModel, ComponentModel>>();
 
        foreach (GraphNode node in container.Kernel.GraphNodes)
        {
            ComponentModel dependingNode = (ComponentModel) node;
 
            foreach (GraphNode depender in node.Dependents)
            {
                ComponentModel dependerNode = (ComponentModel)depender;
 
                if (dependencyPredicate(dependingNode, dependerNode))
                    dependencies.Add(new KeyValuePair<ComponentModel, ComponentModel>(
                        dependingNode, 
                        dependerNode));
            }
        }
 
        return dependencies;
    }
 
    private readonly IWindsorContainer container;
}

Right now WindsorContainerInspector offers only one method: FindDependencies, which looks for dependencies based on the supplied predicate. I already have some additional ideas on what other possible problems to detect in the container, but I’lll write about this next time.