A year ago or so, I had a problem. In my Java forum software (KeyTopic) I wanted to offer the option to let user data come from other sources than KT’s own user database. I didn’t think a lot about it at that time and eventually just decided to only the offer the own user database option. Yesterday, or the day before, “Mark IJbema”:http://www.markijbema.nl pointed me to a post about “Transparent Inversion of Control in Ruby”:http://pragprog.com/pragdave/Tech/TransparentIOC.rdoc. I didn’t find it all too easy to understand, mainly because I don’t know Ruby that well. But after Googling a bit I found “Martin Fowler’s article on Inversion of Control”:http://www.martinfowler.com/articles/injection.html.
The idea, as with any good one, is pretty simple. You apply this pattern if you have different components in your software that you want to keep as autonomous as possible and only bind (connect) at runtime. So, for example in my KeyTopic product I could imagine multiple user data providers:
* The usual database provider (the one that’s used now)
* An “LDAP”:http://www.gracion.com/server/whatldap.html provider (which obtains data from an LDAP directory, like ActiveDirectory)
* A “Passport”:http://www.passport.com provider (Microsoft’s single sign-on service)
Which provider will be used has to be determined at runtime. I can’t tell which one the user of our software will want to use. It has to determined from, for example, an XML file (oh, the joy!).
Time to introduce containers! Computer scientists love the concept of containers. Other people like images. So, observe our initial container:
For simplicity I visualized the rest of KT as one single component. OK, as you can see there are a couple of components swiming in our lil’ container. Ain’t it sweet? Very, yet not that useful. You see dead components. They swim around like regular components. They don’t know they’re dead. You can bring them alive using your XML file. Yep, “XML, once again, brings salvation”:http://www.zefhemel.com/archives/2004/09/12/the-church-of-xml. Depending on the bindings in your XML file, our lil’ container could now look like this:
Amazing, isn’t it. Now, when KeyTopic needs member data, it will know to ask the database provider, as that’s the only component it sees.
Now, let’s take this back to geeky land. I’ll show how this would work in a Python application, which is an obvious choice, given that KeyTopic was written in Java (cough).
allUsers = self.userProvider.findAll()
# Do additional magic here
OK. This is so trivial it’s sick, but bear with me. Even if you don’t know Python you should now aks yourself where self.userProvider came from. That’s a good question. But let’s first look at the three user provider implementations we talked about before (in extreme style pseudo-code):
ldap = LDAP(...)
# Get users from ldap and store them in allUsers
db = mysql.Connection('localhost', 'root', '')
# Get data and put it into allUsers
# I have no idea, seriously
Now it’s time for the magic part… Yes indeed, it’s the XML file (components.xml):
<component id="RestOfKT" class="somemodule.RestOfKT">
<property name="userProvider" local="DBUserProvider" />
<component id="DBUserProvider" class="providers.DBUserProvider"/>
<component id="LDAPUserProvider" class="providers.LDAPUserProvider"/>
<component id="PassportUserProvider" class="providers.PassportUserProvider"/>
Now, to use components from our container (our main programme):
container = PyContainer(config="components.xml")
rest = container.getInstance("RestOfKT")
list = rest.obtainUserList()
When the XML file is loaded by the container it will know that once an instance of the RestOfKT is created, an instance of the DBUserProvider class has to be assigned to its userProvider property. When the instance is created, rest.userProvider will contain an instance of the DBUserProvider class and that will be used to retrieve the users from.
Ain’t that neat?
And you can do this in many languages:
* Python: “PyContainer”:http://www.iem.pw.edu.pl/~sniezynr/pycontainer/
* Java: “Spring”:http://www.springframework.org/ and “PicoContainer”:http://www.picocontainer.org/
* Ruby: “Needle”:http://needle.rubyforge.org/