I’ve blogged a bit in the past, more or less explicitly, about patterns and antipatterns of Inversion of Control usage. This is yet another post that will (possibly) spawn a series. We’ll see about that. Note that this post is not talking about any particular IoC container and what I’m talking about is generic and universally applicable to all of them.
Historically we started to register and configure our components in XML (example is in pseudo syntax, not specific to any particular framework).
<components> <register name="foo" type="Acme.Crm.Foo, Acme.Crm" as="Acme.Crm.IFoo, Acme.Crm" /> <register name="bar" type="Acme.Crm.Bar, Acme.Crm" as="Acme.Crm.IBar, Acme.Crm"> <parameter name="bla" value="42" /> </register> <!--... many, many more components like this...--> </components>
Later we learned that it’s massive pain in the neck to write and manage, and we moved to code:
container.Register<Foo>().As<IFoo>().WithName("foo"); container.Register<Bar>().As<IBar>().WithName("bar") .WithParameter("bla", 42); /* ...many, many more components like this... */
This had the advantage over XML of being strongly typed, refactoring friendly, but still shared the biggest drawback of the previous approach. For each and every new component you added to your application you would have to go back and explicitly register it.
To alleviate that, many containers support conventions – where naming the type in the “right” way or putting it in the “right” place would be enough to make that type available as a component.
container.RegisterAllTypesInNamespace("Acme.Crm") .AsInterfaceTheyImplement() .NamedAsLowercaseTypeName() .WithParameterFor<Bar>("bla", 42);
You would still need to do some finetuning with some of the components, but for bulk of it, it would just work.
So where’s the problem?
While the last approach is evolutionally the most advanced it too comes with its own set of drawbacks, that can bite you if you don’t pay attention to them.
If you partition your application abstracting implementation detains with interfaces and registering classes via conventions you will end up with highly decoupled application. So decoupled in fact, that you will have no explicit reference to your classes anywhere outside your tests. This can complicate things when a new person comes to the project and tries to figure out how stuff works. Simply following ReSharper’s “Go to definition”/”Go to usage” won’t cut it. Often you can infer from the interface or usage which class is used under which interface, but if you have generic abstraction (like ICommandHandler) and multiple implementations that get wired in a non-trivial way it may quickly become much more complicated to find out how the stuff gets wired and why it works the way it does.
Partition your registration
To minimize problems like this its crucial that you partition your registration correctly. What do I mean by that?
First of all make it granular. Most containers have some means of dividing your registration code into pieces. Windsor has installers, Autofac and Ninject have modules, StructureMap has registries… Take advantage of them. Don’t just create one and put all your registration in one. You will regret it when later on you try to change something and you find yourself scrolling through this monstrous class. Make it granular. Partition is applying Single Responsibility Principle. Even if your installer (I will stick to Windsor lingo – replace with module/registry if you’re using any other container) ends up registering just a single type – that’s fine.
Remember to name them correctly so that it’s very quick and obvious having a type, to find which installer is responsible for its registration/configuration.
To do that – also pay attention to the name you give your installers. If the next developer has to stop and think, or search several to find the right one for a particular service you’ve failed.
Last but not least – keep them all in one visible place. I generally create folder/namespace dedicated to holding my installers, so that I can find them quickly and scan the entire list at once. It’s really very frustrating having to chase down installers all across the solution.
Good post, it needs to be told over and over again and the partitioning should not be restricted to just IoC configuration but projects in general. DO partition ALL your code 🙂
That’s so right…
The Single Reponsibility Principle ist applicable almost everywhere.
I’m looking forward reading the next part 😉
I think you make a good point, but could you give a concrete example of how you divide your registries? It strikes me that you could just end up with something that is different, rather than actually better. I don’t really see how dividing up your registries, whilst continuing to use conventions, actually helps the new developer when they get to your code base. Are these two concepts not orthogonal to one another?
I kind of did give an example – look at the screenshot.
This wasn’t the point of the post thought, as other than the obvious ones (ControllersInstaller, ViewsInstaller, RepositoriesInstaller) they tend to be quite specific to the application.
The concepts aren’t completely orthogonal. The way I see it – the more you rely on conventions the less is happening in your code explicitly, the more important it becomes that your registration code is clean, easy to corelate with classes it configures and well structured.
Ah yes, that’s the problem with rss readers. Sorry.