It seems to me that I’ve lately encountered a particular mindset towards Dependency Injection (DI). People seem to think that it’s only really good for replacing one data access implementation with another. Once you get to that point, you know that the following argument isn’t far behind:
“That’s all well and good, but we know for certain that we will never exchange [insert name of RDBMS here] with anything else in this application.”
Apart from the hubris of making such a bold statement about the future of any software endeavor, such a statement reveals the narrow view on DI that its only purpose is for replacing data access components – and perhaps for unit testing.
Those are relevant reasons for using DI, but they are only some of the reasons. Let’s briefly revisit why we employ DI.
We use DI to enable loose coupling.
DI is only a means to an end. Even if you never intend to replace your database and even if you never want to write a single unit test, DI still offers benefits in form of a more maintainable code base. The loose coupling gives you better separation of concerns because it allows you to apply the Open/Closed Principle.
Example coming right up:
Imagine that we need to implement a PrécisViewModel class with a TopSellers property that returns an IEnumerable<string>. To implement this class, we have a data access component. Let’s use the ubiquitous Repository pattern and define IProductRepository to see where that leads us:
public interface IProductRepository
{
IEnumerable<Product> SelectTopSellers();
}
We can now implement PrécisViewModel like this:
public class PrécisViewModel
private readonly IProductRepository repository;
public PrécisViewModel(IProductRepository repository)
if (repository == null)
throw new ArgumentNullException("repository");
this.repository = repository;
public IEnumerable<string> TopSellers
get
var topSellers =
this.repository.SelectTopSellers();
return from p in topSellers
select p.Name;
Nothing fancy is going on here. It’s just straight Constructor Injection at work.
Obviously, we can now implement and use a SQL Server-based repository:
var repository = new SqlProductRepository();
var vm = new PrécisViewModel(repository);
So what does all this loose coupling buy us? It doesn’t seem to help us a lot.
The real benefit is not yet apparent, but it should become more obvious when we start adding requirements. Let’s start with some caching. It turns out that the SelectTopSellers implementation is slow, so we would like to add some caching somewhere.
Where should we add this caching functionality? Without loose coupling, we would more or less be constrained to adding it to either PrécisViewModel or SqlProductRepository, but both have issues:
Since the premise for this post is that we will never use any other database than SQL Server, implementing caching directly in SqlProductRepository sounds like the correct choice, but we would still be violating the SRP, and thus making our code more difficult to maintain.
A better solution is to introduce a caching Decorator like this one:
public class CachingProductRepository : IProductRepository
private readonly ICache cache;
public CachingProductRepository(
IProductRepository repository, ICache cache)
if (cache == null)
throw new ArgumentNullException("cache");
this.cache = cache;
#region IProductRepository Members
public IEnumerable<Product> SelectTopSellers()
return this.cache
.Retrieve<IEnumerable<Product>>("topSellers",
this.repository.SelectTopSellers);
#endregion
For completeness sake is here the definition of ICache:
public interface ICache
T Retrieve<T>(string key, Func<T> readThrough);
The point is that CachingProductRepository extends any IProductRepository we provide to it (including SqlProductRepository) without modifying it. Thus, we have satisfied both the OCP and the SRP.
Just to drive home the point, let us assume that we also wish to record execution times for various methods for purposes of SLA compliance. We can do this by introducing yet another Decorator:
public class PerformanceMeasuringProductRepository :
IProductRepository
private readonly IStopwatch stopwatch;
public PerformanceMeasuringProductRepository(
IProductRepository repository,
IStopwatch stopwatch)
if (stopwatch == null)
throw new ArgumentNullException("stopwatch");
this.stopwatch = stopwatch;
var timer = this.stopwatch
.StartMeasuring("SelectTopSellers");
timer.StopMeasuring();
return topSellers;
Once again, we modified neither SqlProductRepository nor CachingProductRepository to introduce this new feature. We can implement security and auditing features by following the same principle.
To me, this is what loose coupling (and DI) is all about. That we can also replace data access components and unit test using dynamic mocks are very fortunate side effects, but the loose coupling is valuable in itself because it enables us to write more maintainable code.
We don’t even need a DI Container to wire up all these repositories (although it sure would be helpful). Here’s how we can do it with Poor Man’s DI:
IProductRepository repository =
new PerformanceMeasuringProductRepository(
new CachingProductRepository(
new SqlProductRepository(), new Cache()
),
new RealStopwatch()
);
The next time someone on your team claims that you don’t need DI because the choice of RDBMS is fixed, you can tell them that it’s irrelevant. The choice is between DI and Spaghetti Code.
Remember Me
a@href@title, b, em, i, strike, strong
Page rendered at Saturday, February 04, 2012 10:25:45 PM (Romance Standard Time, UTC+01:00)
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.