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 an IoC container. Some even call Service Location as an anti-pattern. Google it.
The most important reason 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 won't be discovered until that edge case is fulfilled.
Using the constructor to inject dependencies will also help you discover violations to the 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
Interface
s are not classes. There is nothing saying that there should be a 1-1 mapping between them. In my opinion, it’s far better to create several small interfaces and let the same class implement them than creating a larger one having all the features that the class has.
Do design the interface
s 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 interface
s also make 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 interface
s).
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 gets easier to handle context sensitive dependencies such as database connections.
Using a longer lifetime (which lives longer than the mentioned context sensitive dependencies) usually means that you create the 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())
{
uow.Save();
}
}
}
instead of:
public class UserService : IUserService
{
public UserService(IUnitOfWork uow)
{
}
public void Add()
{
}
}
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 around that problem. The other simpler approach is to use TransactionScope
.
Both of those solutions are either slower or more complex than services with shorter lifetimes. Save the optimizations to when object instantiation has 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:
class Dbcontext : IDbContext
{
}
class Service1 : IService1
{
}
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. Your 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 interface
s such as IAccountDataSource
and IBillingDataSource
instead of just taking in IDataSource
.
In my opinion, this is a possible violation of Liskov's Substitution principle.