Inversion of control containers – Best practices

Disclaimer: I’ve used IoC containers for a couple of years now and also made a couple of my own (just to learn, nothing released). I’ve also consumed loads of documentation in the form of blogs and stackoverflow questions. And I thought that I should share what I’ve learned. In other words: These are my very own subjective best practices.

An IoC container is not a Service Locator

One of the first mistakes that you’ll make is to register the container in the container or create a singleton to get access to it. It basically means that you can do something like:

public class MyService
{
    MyService(IServiceResolver myContainer)
    {
    }

    public void DoSomething()
    {
        var service = _container.Resolve<ISomeService>();
        service.DoMore();
    }
}

Do not do that. Service location is another pattern that you get as a “bonus” when you use a IoC container. Some even call Service Location as an anti-pattern. Google it.

The most important reasons is that you hide a dependency that you won’t discover until you forget to register IAnotherService. What if the method is only used during edge cases? Then the dependency wont be discovered until that edge case is fulfilled.

Using the constructor to inject dependencies will also help you discover violations to Single Responsibility principle. Having a lot of dependencies might suggest that you need to break your class into several smaller classes.

There are some cases when you THINK that you really need to use the container in your class. Example:

    public class YourService
    {
        public YourService(IContainer container)
        {
        }

        public void YourMethod()
        {
            var user = _container.Get<IUser>();
        }
    }

The proper solution is to register a factory in the container:

    public class YourService
    {
        public YourService(IDomainModelFactory factory)
        {
        }

        public void YourMethod()
        {
            var user = _factory.Create<IUser>();
        }
    }

The smaller interface, the better

Interfaces are not classes. There is nothing saying that there should be a 1-1 mapping between them. imho it’s far better to create several small interfaces and let the same class implement them than creating a larger one having all all features that the class have.

Do design the interfaces before you design the classes. The reason is that if you do the opposite you’ll usually end up with one interface per class.

Smaller interfaces also makes it easier to let your application grow since you can move smaller parts of the implementation (as in creating a new class for one of the interfaces).

Don’t choose a longer lifetime to get better performance

This is a mistake that I’ve done dozens of times. Object creation will usually not be a problem when you are running your application.

The advantage with short lifetimes is that it get easier to handle context sensitive dependencies such as a database connections.

Using a longer lifetime (which lives longer than the mentioned context sensitive dependencies) usually means that you create same kind of service location implementation or custom factories.

Hence you get code like this:

public class UserService : IUserService
{
    public void Add()
    {
        var (var uow = UnitOfWork.create())
        {
            // do stuff.
            uow.Save();
        }
    }
}

instead of

public class UserService : IUserService
{

    public UserService(IUnitOfWork uow)
    {

    }

    public void Add()
    {
        // do stuff with the unit of work here.
    }
}

what’s the problem with the first approach? Well. It’s usually the CALLER that knows what should be done in a unit of work, not the service itself. I’ve seen people create complex Unit Of Work solutions to get round that problem. The other simpler approach is to use TransactionScope.

Both of those solutions are either slower or more complex than services with shorter lifetime. Save the optimizations to when object instantiation have been proven to be a problem.

Don’t mix lifetimes

Mixing lifetimes can lead to undesired effects if you are not careful. Especially when projects have begun to grow and getting more complex dependency graphs.

Example

// EF4 context, scoped lifetime
class Dbcontext : IDbContext
{
}

// short lifetime, takes db context as a dependency
class Service1 : IService1
{
}

// Single instance, keeps a cache etc.
class Service2 : IService2
{
}

Service2 takes service1 as a dependency which in turn requires a dbcontext. Let’s say that the dbcontext has an idle timeout which closes the connection after a while. That would make service1 fail and also service2 which depends on it.

Simply be careful when using different lifetimes.

Try to avoid primitives as constructor parameters

Try to avoid using primitives as constructor parameters (for instance a string). It’s better to take in object since it makes extension (subclassing) easier.

Hey, I need my primitives you say. Take this example:

public class MyRepository
{
    public MyRepository(string connectionString)
    {
    }
}

Well. You’re class breaks SRP. It acts as a connection factory and a repository. Break the factory part out:

public class MyRepository
{
    public MyRepository(IConnectionFactory factory)
    {
    }
}

Avoid named dependencies

Named dependencies are as bad as magic strings. Don’t use them.

If you need different implementations simply create different interfaces such as IAccountDataSource and IBillingDataSource instead of just taking in IDataSource.

imho this is a possible violation of Liskovs Substitution principle.


  • http://www.cuttingedge.it/blogs/steven Steven

    Good set of guidelines.

  • Elkhan

    Hi,
    Nice catches on good practices to follow.
    It would be even better to see some references :)

    • http://www.gauffin.org jgauffin

      What kind of references? As I said in the beginning These are my very own subjective best practices. It’s just my own best practices based on my software architecture university degree, 15 years of experience and creation of several open source frameworks :)