A better IoC pattern

Published on 7/13/2016

A while ago I wrote about interfaces and how everybody just uses it for IoC these days. This time I want to discuss something with regards to established and advertised IoC patterns and problems I have with it.

All the C# projects at Global Kinetic makes use of various libraries from nuget, because "tried and tested" I suppose. One of these is Autofac. This library, along with the likes of log4net, has become almost a staple of the environment here, and no-one questions their use or implementation any more. And while it's an accomplished library that seems to do a good job with generics and so on, it's in my opinion too complicated for what it sets out to do and it's container pattern is flawed. Now, I don't want to focus on bad-mouthing Autofac, but rather to illustrate how using 3rd party libraries unwittingly drives us away from maintaining good principles, while what we should be doing is design something useful, simple and unique.

When you talk about IoC, you also inadvertently talk about SOLID principles. I mean, that is exactly that the "I" stands for. So why do we need it? For unit testing, mostly. A typical plug-in architecture is also based on it for example, but for everyday run-of-the-mill IoC, TTD is where it's at. But lets also look at the "D" part, that is, the "Dependency inversion" principle. This is a much harder to realize principle, since it actually takes some design to achieve.

I consider there to be two levels of "D". First, there's the deep level where we keep different concrete implementations abstract from one another, mostly between different layers or modules. The "I" principle together with treating interfaces as contracts plays a very large role towards achieving this. But then there's the second level, and that is to do with project or module references, which are also dependencies. Some consider this a superfluous thing, but I prefer to keep my inter-layer dependencies to an absolute minimum. I consider it good housekeeping, but there are operational reasons for doing so too*. These two levels together for me is "D".

Lets move back to Autofac and consider their example right there on their front page:


var builder = new ContainerBuilder();

... //some registrations ...

var container = builder.Build();

You have to store the container instance yourself because to start off you have to first resolve something manually. Typically in or around Main() or Application_Start(). You can't have more than one instance, and so the builder has to know about everything. Even if you use the Modules base class and have your bindings on the respective layers (where the concrete implementations are defined), the project where builder is created has to know about all those assemblies where those module classes are. This means that you'll have to include references to every project in your stack, most likely from your top layer. But you'll say "Hey you can scan for assemblies instead, and you can load them from disk dynamically". And this is where I just think this is way too complicated for its purpose. Scanning for assemblies solves a problem that shouldn't have existed in the first place. There are much simpler ways of achieving good, self-managing IoC.

Lets also quickly stop at constructor parameters for a moment. Autofac resolves these for you when you include it in your concrete implementation and provide the appropriate bindings. The thing is, this is an absolute misuse of constructor parameters. Code consists of two different constructs: State-less classes (for example controllers, managers and repositories) which serves to simply group related functions and methods together. And then there are also state-full classes (domain models, ORM entities etc). Constructor parameters' only purpose is to give an initial state to a class. You shouldn't be using it for state-less classes, save for configuration settings which might control logic flow and that you only want to read once (but rather store it statically). Consider the following: By using constructor parameters like Autofac suggests, you pass resolved instances of lower-order dependencies 'down' from above. Doesn't that just reek of not conforming to the "D" principle? I've also seen examples where developers have used Autofac to instantiate state-full domain model instances. Why?! An IoC container is not for factory use, so there should be no need to ever implement any kind of constructor parameter discovery mechanism at all.

A better alternative would be for the assembly containing the lower-order dependency to provide you with an instance, passing it 'up' instead. And only when you ask for it. This solves the spaghetti references hanging around like jungle vines all over the place, but also the other problem of carrying a bunch of resolved instances around in private scope which you might only use in one or two methods, yet requires a list of constructor parameters to set up. I've seen line-lengths in excess of 500 characters!! All in all it's a dirty and unmanageable scenario which permeates every project here because a 3rd party library's documentation says so.

The better IoC implementation that I use is through an adapter pattern. Every layer has an adapter which is responsible for statically setting up the bindings it alone is responsible for. It does not need outside assistance, references, constructor parameters or otherwise. For layers which doesn't get replaced by proxies (typically the core or domain layer), that can be achieved through a single static class inside that assembly. It knows about the shared interface and it's own concrete implementations. The layer above then has the only reference to that assembly, and uses the static class as the resolver. If the same concrete implementation is required in more than one assembly, or for layers that do get replaced by a proxy, I setup a separate adapter assembly which references all the concrete implementations of the shared interface. This acts as a bridge-connector between the referencing layer and the lower assemblies. These adapters are self-managing and sets up the default bindings in the static constructor. No manual resolving is needed if you provide a public getter to use. There is also a generic setter which takes a lamda that constructs a different instance for the typed interface which you can use before calling the getter.

A basic, non-generalized example is the following:


    public static class IoC
    {
        private static Func<IFooRepository> _iFooRepositoryFunction;
    public static void SetFooRepository(Func&lt;IFooRepository&gt; iFooRepositoryFunc)
    {
        _iFooRepositoryFunction = iFooRepositoryFunc;
    }

    public static IFooRepository FooRepo { get { return _iFooRepositoryFunction(); } }




    static IoC()
    {
        SetFooRepository(() => new FooRepository());
    }
}

This is the absolute minimum you need for IoC, and you just don't need anything else for basic projects. It's superbly clean, it's clearly and strictly referenced and isolated and there's no managing of container instances. It is also trivial to extend it to self-manage singleton instances or incorporate configuration elements to control switching bindings at runtime. If you really want to use Autofac however, you can still implement the adapter pattern, but maintain a container instance statically per adapter while managing access through getters (to make sure it uses the correct instance). Regardless, you should never compromise your implementation principles to fit the library you are considering to use.

*The first reason is that you should not have to make changes in your top layer (maintaining bindings) when you make a change in your repo layer. That's an operational dependency which should not be there. Using the Autofac modules prevents this to a large extent though. Secondly, software development typically means you are subject to someone's existing endpoint/service/API, over which you have no influence. At best you can implement the REST client yourself, at worst you are given a DLL that is being developed in parallel. It has been the case in every one of my projects that we'll be working on functionality that the client's endpoint/service/API is not ready to serve yet, or don't even have implemented yet. How do you deal with that? You break the dependency and you build versions that run on proxies that mock out 100% of the endpoint/service/API. In these cases you cannot rely on an assembly being available to reference for your container instance, because half the time your QA cycle will be done on a build with the mock assembly. I guess if you're an amateur you can physically switch the references as you need, but basic IoC should take care of that for you, without the need to reference (i.e. be dependent on) actual bottom-level repository assemblies. In the Autofac world I've seen code to get around this problem by manually instantiating the mocks and feeding them into the constructor as parameters. What's the point of IoC then? It's an atrocious hack, and is a complete disregard for code quality (maintainability in this case) to fit the library implementation which is now just a formality.