Monday, May 14, 2012

Yet Another Dependency Injection Container

Well, this might sound odd to some of you. However, in this post I want to present yet another dependency injection (DI) container.

If you are doing fine with your's feel free to stop reading here. If there are some things that could do better or you are just interested in new options give me a chance.

Due to the fact that the containers feature set isn't that small anymore, this post is only a rough overview of some of its features. Please find a complete documentation attached at the bottom of this post.

Why Yet Another Dependency Injection Framework?

Yes, at first glance FluffyContainer (don't ask why this name) is yet another dependency container. However, FluffyContainer comes across with a combination of two aspects that I've been missing in other dependency injection (DI) frameworks until now.

By my experience there are two different kinds of DI frameworks out there. The one type is feature rich, but relatively slow in creation of objects. The other type are those that are really fast, but poor in features. FluffyContainer combines those two aspects.

Overview of Features

FluffyContainer supports all common DI framework features, like:
  • Type to type mappings (with and without named mappings)
  • Mapping to custom factories
  • Singleton behavior for mappings
  • Calling of parameterized constructors
  • Field injection
  • Property injection
  • XML based mapping configuration
  • In code mapping configuration

In addition to this, it also supports some more advanced features, like:
  • About 20 times faster than other containers
  • Caching of, once initialized, mappings to make the creation of the container very cheap
  • Two types of internal factories. One with focus on high performance and another one that focuses on low-trust environments (like in some ASP.NET environments)
  • Thread Singletons
  • The container and most of its related components are thread safe; including management of singletons
  • Custom type initialization by automatically calling a configurable init method
  • No need of referencing .NET DLLs like System.Web that would require a full .NET framework installation and wouldn't work with the client edition
  • Customization with builders (aka plug-ins)

Performance

One of the main reasons for the development of FluffyContainer was the bad performance of other DI frameworks. If DI can only be used to create the large components but has to be skipped for the smaller once it did not yet reach its target yet.

Whenever people argue that DI is slow, there is somebody else - often somebody who calls himself a matured Software Architect who explains that DI has nothing to do with performance. It is all about abstraction, coupling and composition. I disagree with those Architects! As long as I'm not able to abstract (almost) every component initialization with DI, there are always points of tight coupling that will need to be refactored when a new type hierarchy is required.

Nevertheless, there are always some massively performance critical parts of a system, where DI will most likely never become the shiny hammer. When it comes to arrays instead of lists, emit instead of reflection byte buffers and cost of stack allocation, you are on a point where DI will probably never the way to go. In this scenario, abstraction and composition in general is often the wrong decision. But in all other scenarios, performance should not the reason not to use it.

By default, FluffyContainer is about 20 times faster than the fastest other well-known DI frameworks. On top of this, it provides the possibility to create a factory for a specific mapping. With this factory the creation of components is about 90 times faster than other DI frameworks, while it still provides all features the container. The factory is about 80% (not times) slower than a hardcoded factory, what is a difference I can live with in majority of a system.

Here are some results of my performance tests with some other feature full DI frameworks. The X axis shows the count of items created in my tests, the Y axis shows the duration in milliseconds it took to create the instances.


If you wonder why you see only 3 lines, this is because the lines of "Hardcoded", "FluffyContainer" and "Fluffy Factory" are all overlaying at the bottom of the diagram.

Since this diagram doesn't tell you very much about the three lines on the bottom, here is another diagram that shows only those three competitors.


As you see, out of the box FluffyContainer is about 8.5 times slower than a hardcoded factory. If you know that you will need to create larger amounts of the same component it can be useful to take advantage of the available factories. Especially because the creation of a factory is really cheap.

If you prefer numbers over diagrams, here are my test results.
CountHardcodedFluffyContainerFluffy FactoryOther Fast DIOther Slow DI
500000109218158015106
10000002017535322030281
15000002825252463844591
20000004734574617261123

I'm not going to talk about the names of the other DI frameworks I used in this test since this is no bashing for any other framework. But trust me, the fast one is one of the fastest, serious DI frameworks.

Configuration

Here is a quick overview of how to configure the container in XML or in code.

FluffyContainer supports two different kinds of configuration; XML and a expression based in-code mapping syntax. Here is a short sample of both configurations.

XML
<?xml version="1.0" encoding="utf-8" ?>
<dependencies>
   <assemblies>
      <assembly name="System.Data, 
                      Version=4.0.0.0,
                      Culture=neutral,
                      PublicKeyToken=b77a5c561934e089"/>
      <assembly name="DependencyInjectionSamples"/>
   </assemblies>
   <namespaces>
      <namespace name="System.Data.Common"/>
      <namespace name="System.Data.SqlClient"/>
      <namespace name="DependencyInjectionSamples"/>
   </namespaces>
   <mappings>
      <map from="IMyComponent" to="MyComponent"/>
      <map from="DbConnection" to="SqlConnection" name="db1"/>
      <map from="IMyComponent" toFactory="MyComponentFactory" name="factory"/>

      <map from="IMyComponent" to="MyComponent" 
           singleton="appDomain" name="singleton"/>
      <map from="IMyComponent" to="MyComponent" 
           singleton="thread" name="threadSingleton"/>

      <map from="MyComponent" to="MyComponent" name="fpConst">
         <field name="_foo" value="123"/>
         <property name="Id" value="1"/>
      </map>
 
      <map from="MyComponent" to="MyComponent" name="fpMapped">
         <property name="Database" from="DbConnection"/>
      </map>
   </mappings>
</dependencies>
<!--
Read XML in C#
var resolver = XmlDependencyResolver.FromFile("DependencyInjection.xml");
FluffyContainer container = new FluffyContainer(resolver);
-->
C#
using FR.DependencyInjection;

...

FluffyContainer container = new FluffyContainer();
container.Map<IMyComponent>().To<MyComponent>();
container.Map<DbConnection>().Named("db1").To<SqlConnection>();
container.Map<IMyComponent>().Named("factory").ToFactory<MyComponentFactory>();

container.Map<IMyComponent>().Named("singleton").AsSingleton().To<MyComponent>();
container.Map<IMyComponent>().Named("thread").AsThreadSingleton().To<MyComponent>();

container.Map<IMyComponent>()
         .Named("fpConst")
         .WithField("_foo", 123)
         .WithProperty("Id", 1)
         .To<MyComponent>();

container.Map<IMyComponent>()
         .Named("fpMapped")
         .WithProperty("Database", typeof(DbConnection), null);

When using XML mappings, you are still able to add additional in-code mappings in code, after the container was initialized from an XML.

Usage

After configuring the container, you can use it in your project source code by using the Resolve method to create new instances. Use the Release method to ensure a correct clean-up of IDisposable implementing components, while taking singleton configurations into account.

var comp = container.Resolve<IMyComponent>();
// use comp
container.Release<IMyComponent>(comp);

To simplify error handling for disposable components, especially when working with more than one resource, consider using ActivationScope when resolving several components. The activation scope will automatically manage a disposing of all resolved instances when it runs out of scope.
using (ActivationScope scope = container.CreateScope()) {
   var comp2  = scope.Resolve<IMyComponent>();
   // do work
   scope.Release<IMyComponent>(comp2);
   // disposing of comp2 will be skipped when the scope becomes disposed
 
   // ..
 
   var mydb = scope.Resolve<DbConnection>();
   // do work
   // mydb becomes automatically disposed when the scope becomes disposed
}

Conclusion

As I stated at the beginning of this post; yes, this is yet another dependency injection (DI) framework. Nevertheless it is relatively grown in its set of features and its performance blows all other frameworks (that I know) out of the building. If you already successfully work with DI in your project, stay with whatever you use. If you start a new project or if you are completely new in DI, consider to give it a chance.

Don't forget to check the documentation related to this post that will describe all features of the framework in deep.

Download

FluffyContainer is part of my core libraries that I've published in another post, FR.Core Libraries.

Here you can find all files in the version that was used in this post:

6 comments:

  1. Hi Flo,

    though I think that most (really? :-)) of the code is in my mind, I'll have a look at that. Some things like the singleton attribute sounds new to me.

    Thanks for your articles about this great lib and the discussions about it in the past.

    Sirch

    ReplyDelete
    Replies
    1. Quite sure, this code is not what you got in your mind. ;)

      Thanks for the nice feedback.

      Flo

      Delete
  2. It runs very fast but crashes if no default constructor is available.
    Try this:

    public class Test
    {
    // no default constructor here, throws error

    public Test(INeedSome needSome)
    {

    }
    }

    public INeedSome { bool Need { get; set; } }

    ReplyDelete
    Replies
    1. Yupp, if not configured.
      Check out chapter "Constructor Injection" (page 25) of documentation on how to configure constructor parameters.

      [off topic]
      Servus Peter
      Kennst' mich noch aus dem myCSharp Forum? ;-)
      Grüße
      Flo

      Delete
  3. Allright. Why no automatic wireup constructor parameters?
    I find it more than annoying, to declare every construcotor parameter.
    Additionaly, i don`t know a single container instantly, that works at the same way as yours in this point.

    I used my Performace test which is packed within LightCore. But with this new information, i see now that FluffyContainer brings me only one instance back without injections.

    So surely it will be fast :-)

    [off topic]
    Ja klar. Kam über deine Signatur hierher :-)

    ReplyDelete
    Replies
    1. Sorry for late answer, been busy.

      From my view, the knowledge about the available constructor breaks the abstraction between the IOC container and the consumer.

      However, I checked MEL's UnityContainer and Ninject. Unity does not provide constructor injection when resolving a component. On the other hand Ninject (what is not my favorite, though) provides this feature.

      Gonna check the effort for this extension. Expect an update within the next week or two.

      (The performance has nothing to do with missing injection options, you just have to configure the container - in XML or fluent. It's caused by the dynamic code generation ;-) )

      Cheers
      Flo

      Delete

Note: Only a member of this blog may post a comment.