I recant my IoC! IoC containers in dynamic languages are silly.


After a year or so of solid Alt Dot Net infection (as far as infections go its a pretty awesome one to have), I decided to give Python a try again for more than one off sysadmin tasks, and to actually dive into it as a newly minted “Agilista”. 

However, I had  a problem..there were no non-painful IoC containers in Python (sorry to the other authors of IoC frameworks in Python like Spring Python and Snake Guice, I know you try and I respect the effort).  Ultimately, I could **not**  imagine coding anymore without something to handle all my registration for me, that’d dynamically inject in my dependencies, give me hooks for contextual resolution, and give me rich interception support.

So I built my own, it was painful still, but I had some ideas to move it in an Convention Over Configuration direction, and ultimately get within shooting range of what fancy stuff we’ve come to expect in .Net that Windsor, StructureMap and many others provide.

Now as I got into the fancier aspects of dynamic languages, open classes and the ability to override all behavior easily I get it…a dynamic languages runtime is like a bit IoC container.

Now I’ve heard other people say this many times but rarely with explanation or example, or focus on frankly silly things like “Dependency Injection adds too many lines of code” which is a bit melodramatic. Two arguments to a constructor, two fields ,and then avoiding having to call new is not “adding too many lines of code”, especially with templates, IDE’s, scripting, etc to cut down the actual typing load.Today I’m going to actually try to explain how languages like Python, Ruby etc give us the same awesomeness we’ve come to expect in things like Windsor..but at a much cheaper cost of learning and dev time.

Take a typical resolution scenario where you want to output to a different file format depending on command line switches.  With an IoC container you can either:

Change the resolution argument to load a different concrete type:

if(arg == “XML”)
{
  container.Register(Component.For().ImpmentedBy());
}
else if(arg == “HTML”)
{
  container.Register(Component.For().ImpmentedBy());
}
else
{
  container.Register(Component.For().ImpmentedBy());
} </div> </div>

or resolve different arguments or keys using a service locator approach in later client code (thereby depending on the container)</p>

public void output(IKernel container, string key)
{
var output =container.Resolve(key);
output.save();</p>

} </div> </div> </blockquote>

or implement a custom implementation of the resolver system (which I’ll leave out for the sake of brevity, but it’s not instant code).  Also to do all this you have to depend on interfaces, add all your interchangeable code to the constructor and life is grand.  You do this for many reasons in static languages, its the only way to get easy testability and code that is open to extension.  In dynamic languages its always open for extension and easy to test . Let me demonstrate:

import outputlib as o</p>

def outputselect(arg):
if arg == “XML”:
o.Output = XmlOutput
elif arg == “HTML”:
o.Output = HtmlOutput
else:
o.Output = NullOutput

def saveoutput():
o.Output().save()  #will save whichever </div> </div> </blockquote>

Contextual resolution in a nutshell, and throughout your code if need be.  “Interception” is even easier, take a look at http://docs.python.org/dev/library/functools.html and then start playing you’ll see you can trivially apply logging and security to methods without explicitly adding it. A short logging example follows:

import types 
import functools</p>

#applies a cepter to each non-underscored method.
def wrapcls(cls, cepter):
    publics  = [ name for name in dir(cls) if not name.startswith(“_”)] 
    methods = [getattr(cls,method) for method in publics if type(getattr(cls,method)) == types.MethodType ]
    for method in methods:
        intercepted_method = cepter(method)
        setattr(cls, method.__name__, intercepted_method) #attaches intercepted_method to the original class, replacing non-intercepted one

#the magic all happens in the functools.wraps decorator
def loggingcepter(func):
    @functools.wraps(func)
    def logafter(*args, **kwargs): #for csharp devs view this as an inline delegate
        result = func(*args, **kwargs) #invoking function
        print “function name: ” + func.__name__
        print “arguments were: ”
        for a in args:
            print repr(a)
        print “keyword args were: ”
        for kword in kwargs:
           print repr(kword) + “ : ” + repr(kwargs[kword])
        print “return value was: ” + repr(result)
        return result
    return logafter
    
#default boring repository class
class StorageEngine(object):
    @property
    def connections(self):
        pass
    
    def _sessioncall(self):
        pass
        
    def create(self,user):
        print “running create now from the method”
         
    def delete(selfid):
        print “running delete now from the method”
        return “deleted from the database”
        
    def get(selfid):
        pass
        
    def __init__(self):
        pass

#placeholder storage object
class User(object):
    pass
            
wrapcls(StorageEngine ,loggingcepter)
repo = StorageEngine()
print “calling count this should not be intercepted”
cnncount = repo.connections
print “now get should be intercepted”
repo.get(1)
print “we should see keyword arguments here”
repo.delete(id=2)
print “session call should not be intercepted”
repo._sessioncall()
print “create should be intercepted and we should see a User object”
repo.create(User()) </div> </div> </blockquote>

 

Running the above script should result in the following output

 

Screen shot 2009-11-16 at 4.20.56 PM

So this is all very cool, but what does it mean or why do I care?  For those of us used to using a proper IoC container like Windsor or StructureMap, we’ve gotten used to have capabilities like the above easily available to us when we’ve needed them. It’s nice to find that in Python (or really any dynamic language ) we can easily build our own similar functionality that we’ve come to depend on.  We’re never coupled and we’re always able to test and mock out behavior.  It was a long time coming but I think I finally get it now.

Alt.Net Evangelism And What We Could Do Better