When to use Dependency Inversion Principle in application?
A medium or large size application can easily get complex with tight coupling in between different layers of the application. In this post, I’ll tell you when to use Dependency Inversion Principle in application in order to decouple the layers. Let’s see what Uncle Bob has to say on this.
Bob Martin’s Paper
Read his Article in C++ Report May 1996 as he invented this principle and also gave some examples.
Here is an summary of what Uncle Bob said:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend upon details. Details should depend upon abstractions.
Let us understand both of above parts in detail. I’ll be reusing existing code from my previous post when to use Unit of Work pattern in application and improve it using this principle.
Part 1
The EventsController
class definition is mentioned below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class EventsController : Controller { private readonly ApplicationDbContext _context; private readonly UnitOfWork _unitOfWork; public EventsController() { _context = new ApplicationDbContext(); _unitOfWork = new UnitOfWork(_context); } public ActionResult Details(int id) { var event = _unitOfWork.Events.GetEvent(id); if (event == null) return HttpNotFound(); var viewModel = new EventDetailsViewModel { Event = event }; return View("Details", viewModel); } [Authorize] public ActionResult MyEvents() { var userId = User.Identity.GetUserId(); var events = _unitOfWork.Events.GetUpcomingEventsByArtist(userId); return View(events); } } |
In a web application context, Controller
is a high-level module and UnitOfWork
is a low-level module. In the code above, Controller
is tightly coupled with UnitOfWork
that violates the dependency inversion principle. That means in cases whenever we change UnitOfWork
, the Controller
may need some changes as well or at-least it will need to be recompiled and assembly will need to be redeployed. This is a blocker for independently deploying assemblies.
With the help of this principle, Controller
can depend on an abstraction of UnitOfWork
i.e. an interface IUnitOfWork
that will be implemented by UnitOfWork
. So, as long as the contract remains same, controller doesn’t need care about any implementation changes in concrete UnitOfWork
class. For example, the persistence layer can easily change from one version of Entity Framework to another or to another ORM, say NHibernate and the controller doesn’t need to know about this change. Plus, it doesn’t need recompilation.
So with concept above, the code looks like following now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class EventsController : Controller { private readonly IUnitOfWork _unitOfWork; public EventsController() { _unitOfWork = new UnitOfWork(new ApplicationDbContext()); } public ActionResult Details(int id) { var event = _unitOfWork.Events.GetEvent(id); if (event == null) return HttpNotFound(); var viewModel = new EventDetailsViewModel { Event = event }; return View("Details", viewModel); } [Authorize] public ActionResult MyEvents() { var userId = User.Identity.GetUserId(); var events = _unitOfWork.Events.GetUpcomingEventsByArtist(userId); return View(events); } } |
Part 2
The UnitOfWork
class and IUnitOfWork
interface used in the code above looks like following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class UnitOfWork { private readonly ApplicationDbContext _context; public EventRepository Events { get; private set; } public UnitOfWork(ApplicationDbContext context) { _context = context; Events = new EventRepository(context); } public void Complete() { _context.SaveChanges(); } } |
1 2 3 4 5 |
public interface IUnitOfWork { EventRepository Events { get; } void Complete(); } |
This interface directly depends on EventRepository
which internally depends on a DbContext
class and Entity Framework. So, this abstraction i.e. IUnitOfWork
is dependent on details i.e. EventRepository
as it is a concrete implementation. So, IUnitOfWork
is not a good abstraction as even though controller is dependent on an abstraction, it is still tightly coupled with a repository and entity framework. So, to fulfil part 2 of dependency inversion principle, we can replace the usage of EventRepository
with IEventRepository
that knows nothing about entity framework as shown below. So now abstraction doesn’t depend on detail.
1 2 3 4 5 |
public interface IUnitOfWork { IEventRepository Events { get; } void Complete(); } |
Just to show, the definition of IEventRepository
looks like following:
1 2 3 4 5 |
public interface IEventRepository { Event GetEvent(int eventId); IEnumerable<Event> GetUpcomingEventsByArtist(string artistId); } |
The code looks quite good now as it is using interfaces for abstraction and we are now following dependency inversion principle.
But wait, you may think that controller is still tightly coupled with entity framework as in the code above, it is controller’s responsibility to provide an instance of ApplicationDbContext
class to UnitOfWork
class instance. To completely remove this tight coupling, we need Dependency Injection (DI) concept in application.
I’ll write a blog post soon about this. So make sure you are subscribed for updates! This sums up my post to explain when to use Dependency Inversion Principle in application.
Pingback: How to use Autofac for Dependency Injection in ASP.NET MVC application? - Siddharth Pandey()