Search This Blog

2011-04-30

The gateway for an IoC container

Inversion of control and Dependency injection tools became an integral part of any enterprise application because they are great at reducing complex dependencies and increasing flexibility.
My first experience with IoC within enterprise applications was MS Unity that had been used  for decoupling parts of a system and adding extra-flexibility (e.g., to extend system with new implementations). As I discovered it is a great tool that helped me and my team a lot to meet the flexibility requirements of our solution. Nevertheless during research and development I found several issues of using IoC/DI tool in general. They might not be so obvious, but they became a bone in the throat for me.
1. If there is a necessity to implement extra-caching for some services (i.e., specific for our CMS) it would a hard time to create and extensions to do that, and our solution was to create a wrapper layer over Unity's container to intercept its calls and with the passage of time that layer had become pretty big and messy.
2. There is a negative dependency on API of the tool. Thus, Unity's xml container configuration had become our public interface for CMS developers to extend our solution and, as a result, our solution was dependent on reliability of Unity and more - it meant freezing of version Unity. Why? If customer invest in a solution based on our system he would definitely use our (actually, borrowed from Unity) xml structure and it would take time and money to modify it according to the new version of Unity. It did not take long to feel it in the hard way: Unity had been upgraded from version 1.2 to 2.0 so it took us a week to upgrade not released product!
3. IoC tools use reflection (emit is promised) to create and an instance(s) (even though using Singletons) so Resolving is always in the performance hot-sports (8-12% of CPU general usage! for our project). Solving the problem requires extra-monitoring and optimizing of using container. Basically such a problem happened because our team had become to use DI tool as a ServiceLocator instead of injecting dependencies that caused using Container.Resolve<T>() enormous amount of times. If you use 3-d party unmanaged tool is more complex to control resolving.
For my new project we have looked to some various DI tools like: Managed Extensibility Framework (it is actually a little bit different but, at some point, can be used as DI tool), autofac, Spring, Ninject, StructureMap and Windsor. The decision was to use Castle Windsor that, at first, disappointed me but than I realized that all tools basically able to do the same :-). So being inspired by Ritesh Rao's post and Davy Landman's one I realized that sometimes it makes sense to use a generic gateway wrapper for IoC container. It would allow to "mitigate" described issues plus benefits like tool version/vendor independence, more control for 3-d party tool as an "enemy". Of course there is a great tool called Common Service Locator library but, unfortunately, it had been released more than 2 years ago and it does not fit all the functionally we require from the DI tool.
So I have decided to develop some simple gateway that would fit major functionalities I faced for the IoC.
Such a wrapper has several disadvantages:
1. It cuts extra-functionalities of a tool beyond;
2. It takes time to develop and maintain it.
So, IMHO, for small projects it does not make any sense to use any wrappers, it will just increase the complexity. But for enterprise applications that are developed by many people for many years it will give distinct advantages.
Functionalities cuts is a very ambiguous thing. Many tools have extra-functionality like class interceptors, easy-registrations etc. IMHO, these bells and whistles might harm more than give benefits. Of course some of features are important so they should be considered to be used on enterprise approaches (like AOP and PostSharp as a tool).

Ok, let's look to the wrapping.
Basic solution is quite simple: there is container's core that has an instance(s) of the 3-d party tool to intercept all calls and static gateway for quick and convenient access to the IoC container. The first goal is completely isolate 3-d party tool for the our usage.
Okay, before we start on the IoC wrapper, I need some infrastructure work done. First we need is somehow to initialize the container i.e. to fulfill it with contract-implementation associations. One of the most simple solution is create a interface that allows to build the container.
public interface IIoCRegistration
{
  void BuildContainer(IoCContainerCore container);
}

[IoCRegistration]
public class Registrator : IIoCRegistration
{
  public void BuildContainer(IoCContainerCore container)
  {
    container.Register();
  }
}
The class that implements this interface will receive container instance and than it is possible to initialize it using API, either read from XML or anything else. So idea is to let user to implement the interface and mark classes with custom attribute to find registration in an assembly:
[AttributeUsage(AttributeTargets.Class)]
public sealed class IoCRegistrationAttribute : Attribute
{
}
Ok, but we also need to register that assemblies with container setup: to write their names to application config file. So let's create a section:
public sealed class IoCAssemblyRegistration : ConfigurationSection
{
  public const string ConfigurationSectionName = "iocAssemblyConfiguration";

  [ConfigurationProperty("assemblies")]
  public AssembliesElementCollection Assemblies
  {
    get
    {
      return (AssembliesElementCollection)this["assemblies"] ?? new AssembliesElementCollection();
    }
  }

  public static IoCAssemblyRegistration GetConfig()
  {
    return (IoCAssemblyRegistration)ConfigurationManager.GetSection(ConfigurationSectionName) ?? new IoCAssemblyRegistration();
  }
}

public class AssemblyElement : ConfigurationElement
{
  [ConfigurationProperty("assembly", IsRequired = true)]
  public string Assembly
  {
    get
    {
      return this["assembly"] as string;
    }
  }
}

public class AssembliesElementCollection : ConfigurationElementCollection
{
  public AssemblyElement this[int index]
  {
    get
    {
      return BaseGet(index) as AssemblyElement;
    }

    set
    {
      if (BaseGet(index) != null)
      {
        BaseRemoveAt(index);
      }

      BaseAdd(index, value);
    }
  }

  protected override ConfigurationElement CreateNewElement()
  {
    return new AssemblyElement();
  }

  protected override object GetElementKey(ConfigurationElement element)
  {
    return ((AssemblyElement)element).Assembly;
  }
In the .config file it will look like:

  
    
Ok, infrastructural work is done, let's define what basic functionality we would like to have:
1. Nested containers;
2. Registration of type and instance;
3. Resolve, resolve all;
4. Checking if registered;
5. Remove registrations;
6. Setup lifetime management.

Also we need to have a thread save container and, since we suppose that registering (writing to the container) would invoked rarely and resolving (reading) is a common usage, ReaderWriterLockSlim class can be used.

....
private readonly ReaderWriterLockSlim iocLocker;
....
private void InvokeWriteLock(Action action)
{
  this.iocLocker.EnterWriteLock();

  try
  {
    action();
  }
  catch (Exception exception)
  {
    Log.Error(exception.Message, exception, typeof(IoCContainerCore));
    throw new InvalidOperationException(exception.Message, exception);
  }
  finally
  {
    this.iocLocker.ExitWriteLock();
  }
}

private T InvokeReadLock(Func func)
{
  if (!this.iocLocker.IsReadLockHeld && !this.iocLocker.IsWriteLockHeld)
  {
    this.iocLocker.EnterReadLock();
  }

  try
  {
    return func();
  }
  catch (Exception exception)
  {
    Log.Error(exception.Message, exception, typeof(IoCContainerCore));
    throw new InvalidOperationException(exception.Message, exception);
  }
  finally
  {
    if (this.iocLocker.IsReadLockHeld)
    {
      this.iocLocker.ExitReadLock();
    }
  }
}
...

1. Nested containers. So let's allow to have a  hierarchy of containers:
public partial class IoCContainerCore : IDisposable
{
  private readonly ReaderWriterLockSlim childrenLocker;

  private IWindsorContainer container;

  private IoCContainerCore parent;

  private string name;

  private Dictionary childContainers;

  public IoCContainerCore()
  {
    this.container = new WindsorContainer();
    this.childrenLocker = new ReaderWriterLockSlim();
    this.childContainers = new Dictionary();
  }

  public IoCContainerCore Parent
  {
    get
    {
      return this.parent;
    }

    set
    {
      Assert.ArgumentNotNull(value, "value");
      this.parent = value;
      Log.Info(string.Format("Parent container named: '{0}' has been set.", value.Name), typeof(IoCContainerCore));
    }
   }

  public string Name
  {
    get
    {
      return this.name;
    }
    set
    {
      Assert.ArgumentNotNullOrEmpty(value, "value");
      this.name = value;
      Log.Info(string.Format("Container name: '{0}' has been set.", value), typeof(IoCContainerCore));
    }
 }

 public virtual void AddChildContainer(string childContainerKey, IoCContainerCore childContainer)
  {
    Assert.ArgumentNotNullOrEmpty(childContainerKey, "childContainerKey");
    Assert.ArgumentNotNull(childContainer, "childContainer");

    this.childrenLocker.EnterWriteLock();
    try
    {
      Assert.IsFalse(this.childContainers.ContainsKey(childContainerKey), string.Format("Child container named: '{0}' has already been added.", childContainerKey));

      this.childContainers.Add(childContainerKey, childContainer);

      Log.Info(string.Format("Child container named: '{0}' has been added.", childContainerKey), typeof(IoCContainerCore));
    }
    catch (Exception exception)
    {
      Log.Error(exception.Message, exception);
      throw;
    }
    finally
    {
      this.childrenLocker.ExitWriteLock();
    }
  }

  public virtual void RemoveChildContainer(string childContainerKey)
  {
    Assert.ArgumentNotNullOrEmpty(childContainerKey, "childContainerKey");

    this.childrenLocker.EnterWriteLock();
    try
    {
      Assert.IsFalse(this.childContainers.ContainsKey(childContainerKey), string.Format("Child container named: '{0}' has already been added.", childContainerKey));

      this.childContainers.Remove(childContainerKey);

      Log.Info(string.Format("Child container named: '{0}' has been removed.", childContainerKey), typeof(IoCContainerCore));
    }
    catch (Exception exception)
    {
      Log.Error(exception.Message, exception);
      throw;
    }
    finally
    {
      this.childrenLocker.ExitWriteLock();
    }
  }

  public virtual IoCContainerCore GetChildContainer(string childContainerKey)
  {
    Assert.ArgumentNotNullOrEmpty(childContainerKey, "childContainerKey");

    this.childrenLocker.EnterReadLock();
    try
    {
      Assert.IsTrue(this.childContainers.ContainsKey(childContainerKey), string.Format("Child container named: '{0}' does not excist.", childContainerKey));

      return this.childContainers[childContainerKey];
    }
    catch (Exception exception)
    {
      Log.Error(exception.Message, exception);
      throw;
    }
    finally
    {
      this.childrenLocker.ExitReadLock();
    }
  }
}
2. Registration. I have a list of registration functionality that I usually use.
1) Register by type: common use case;
2) Register by key: required when you have set of implementation ;
3) Register object instance: useful when object creation is not under your control;
4) Register with injecting parameters: setup additional parameters to the constructor;
5) Register with lifetime management: by default all registrations are singletons, but usually and especially for web application another management required like: per Request, per Session, per Thread. etc ;
6) Register with setting up dependencies: allow user to setup what exactly dependency should be used;
7) Register using factory method: useful when an object should be created in a special way (e.g. NHibernate data session).
There is a list a basic examples without combination of parameters.

public virtual void Register<TFrom, TTo>() where TTo : TFrom
{
  this.InvokeReadLock(() => this.container.Register(Component.For<TFrom>().ImplementedBy<TTo>()));
}

public virtual void RegisterInstance<TFrom>(TFrom fromInstance)
{
  Assert.ArgumentNotNull(fromInstance, "@from");

  this.InvokeReadLock(() => this.container.Register(Component.For<TFrom>().Instance(fromInstance)));
}

public virtual void Register<TFrom, TTo>(string key) where TTo : TFrom
{
  Assert.ArgumentNotNullOrEmpty(key, "key");

  this.InvokeReadLock(() => this.container.Register(Component.For<TFrom>().ImplementedBy<TTo>().Named(key)));
}

public virtual void Register<TFrom, TTo>(IEnumerable<KeyValuePair<string, string>> dependencies) where TTo : TFrom
{
  Assert.ArgumentNotNullOrEmpty(dependencies, "dependencies");

  this.InvokeReadLock(() =>
  {
    var windsorDepens = dependencies.Select(parameter => ServiceOverride.ForKey(parameter.Key).Eq(parameter.Value));

    return this.container.Register(Component.For<TFrom>().ImplementedBy<TTo>().DependsOn(windsorDepens.ToArray()));
  });
}

public virtual void Register<TFrom, TTo>(Action<IDictionary> setParameters) where TTo : TFrom
{
  Assert.ArgumentNotNull(setParameters, "setParameters");

  this.InvokeReadLock(() => this.container.Register(Component.For<TFrom>().ImplementedBy<TTo>().DynamicParameters((k, d) => setParameters(d))));
}

public virtual void Register<TFrom, TTo>(ILifeTimeDefinition lifeTimeDefinition) where TTo : TFrom
{
  Assert.ArgumentNotNull(lifeTimeDefinition, "lifeTimeDefinition");

  this.InvokeReadLock(() => this.container.Register(LifeTimeRegistration.GetManager(Component.For<TFrom>().ImplementedBy<TTo>(), lifeTimeDefinition)));
}

public virtual void Register<TFrom>(Func<TFrom> factoryMethod)
{
  Assert.ArgumentNotNull(factoryMethod, "factoryMethod");

  this.InvokeReadLock(() => this.container.Register(Component.For<TFrom>().UsingFactoryMethod(() => factoryMethod())));
}
...

So let's move to resolving.
1) Resolving by type
2) Resolving by type and key;
3) Resolve all;
4) Resolve injecting arguments;
5) Try resolve

public virtual T Resolve<T>()
{
  return this.InvokeReadLock(() =>
  {
    this.EnsureContainerIsInitialsied();
    return this.container.Resolve<T>();
  });
}

public virtual T Resolve<T>(string key)
{
  Assert.ArgumentNotNullOrEmpty(key, "key");

  return this.InvokeReadLock(() =>
  {
    this.EnsureContainerIsInitialsied();
    return this.container.Resolve<T>(key);
  });
}

public virtual T Resolve<T>(IDictionary arguments)
{
  Assert.ArgumentNotNull(arguments, "arguments");

  return this.InvokeReadLock(() =>
  {
    this.EnsureContainerIsInitialsied();
    return this.container.Resolve<T>(arguments);
  });
}

public virtual T Resolve<T>(object argumentsAsAnonymousType)
{
  Assert.ArgumentNotNull(argumentsAsAnonymousType, "argumentsAsAnonymousType");

  return this.InvokeReadLock(() =>
  {
    this.EnsureContainerIsInitialsied();
    return this.container.Resolve<T>(argumentsAsAnonymousType);
  });
}

public virtual object Resolve(Type service)
{
  Assert.ArgumentNotNull(service, "service");

  return this.InvokeReadLock(() =>
  {
    this.EnsureContainerIsInitialsied();
    return this.container.Resolve(service);
  });
}

public virtual bool TryResolve<T>(out T @object)
{
  return this.TryInvokeReadLock(
  () =>
  {
    this.EnsureContainerIsInitialsied();
    return this.container.Resolve<T>();
  },
  out @object);
}

public virtual T[] ResolveAll<T>()
{
  return this.InvokeReadLock(() =>
  {
    this.EnsureContainerIsInitialsied();
    return this.container.ResolveAll<T>();
  });
}

private void EnsureContainerIsInitialsied()
{
  if (!this.initialised)
  {
    this.InitializeContainer();
  }
}
...

As you can see an idea is simple but it's painstaking :-)
I'll skip blocks with checking if registered and removing registrations because it is quite trivial task.
And couple of words about lifetime management. In order to isolate DI tool classes I had to create own wrappers based on Lifetime definition and manager:

public interface ILifeTimeDefinition
{
  Type LifetimeManager { get; }
}
public static class LifeTimeRegistration
{
  public static ComponentRegistration<S> GetManager<S, T>(ComponentRegistration<S> componentRegistration,T definition) where T : ILifeTimeDefinition
  {
    return new LifestyleGroup<S>(componentRegistration).Custom(definition.LifetimeManager);
  }
}
public class PerWebRequest : ILifeTimeDefinition
{
  public Type LifetimeManager
  {
    get { return typeof(PerWebRequestLifestyleManager); }
  }
}
public class PerThread : ILifeTimeDefinition
{
  public Type LifetimeManager
  {
    get { return typeof(PerThreadLifestyleManager); }
  }
}
...

And now a static gateway example. It is made just for initialization, storing wrapper and convenience.

public static class IoCContainer
{
  private static readonly IoCContainerCore Container;

  static IoCContainer()
  {
    Container = new IoCContainerCore();
  }

  public static IoCContainerCore ContainerCore
  {
    get { return Container; }
  }

  public static T Resolve()
  {
    return Container.Resolve();
  }

  public static void Register() where TTo : TFrom
  {
    Container.Register();
  }
  ....
  public static void InitializeContainer()
  {
    if (Container.IsInitialised)
    {
      return;
    }

    IEnumerable configAssemblies = GetConfigAssemblies();
    if (configAssemblies.Any())
    {
      Container.InitializeContainer(configAssemblies, null);
    }
    else
    {
      Container.InitializeContainer();
    }
  }
   
  private static IEnumerable GetConfigAssemblies()
  {
    var section = ConfigurationManager.GetSection(IoCAssemblyRegistration.ConfigurationSectionName) as IoCAssemblyRegistration;
    if (section == null)
    {
      // throw new ConfigurationErrorsException("Configuration section was not found");
      yield break;
    }

    foreach (AssemblyElement element in section.Assemblies)
    {
      yield return element.Assembly;
    }
  }

}

Ok, and that works, but is it possible to change the DI tool? Yes, and it took my two and a half hours to replace Windsor with Unity and pass all unit tests.
IoC container usage is quite simple:
[TestFixture]
public class IoCContainerTest
{
  [SetUp]
  public void SetUp()
  {
    IoCContainer.InitializeContainer();
  }

  [TearDown]
  public void ResetContainer()
  {
    IoCContainer.ResetContainer();
  }
  
  [Test]
  public void ResolveTest()
  {
    var service = IoCContainer.Resolve();

    Assert.IsNotNull(service);
    Assert.IsTrue(service is Logger);
  }

  [TestCase("Implementation1")]
  [TestCase("Implementation1Child")]
  public void ResolveInvokeTest(string serviceName)
  {
    var service = IoCContainer.Resolve(serviceName);

    Assert.IsNotNull(service);
    service.Do("some data");
  }
 ...
}
As a summary, I want to underline what that messy stuff gave us. We have isolated 3-d party tool completely so we control IoC container usage now :-), we can do any manipulations to optimize it: caching, container storing, combining several tools if needed, lifetime managers manipulations.
Sources are available here for Windsor and here for Unity.

1 comment: