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:

  • First of all we would be violating the Single Responsibility Principle (SRP) in both cases.
  • If we implement caching in PrécisViewModel, other consumers of the SelectTopSellers would not benefit from it.
  • If we implement caching in SqlProductRepository, it wouldn't be available for any other IProductRepository implementations.

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;
    private readonly IProductRepository repository;
 
    public CachingProductRepository(
        IProductRepository repository, ICache cache)
    {
        if (repository == null)
        {
            throw new ArgumentNullException("repository");
        }
        if (cache == null)
        {
            throw new ArgumentNullException("cache");
        }
 
        this.cache = cache;
        this.repository = repository;
    }
 
    #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 IProductRepository repository;
    private readonly IStopwatch stopwatch;
 
    public PerformanceMeasuringProductRepository(
        IProductRepository repository, 
        IStopwatch stopwatch)
    {
        if (repository == null)
        {
            throw new ArgumentNullException("repository");
        }
        if (stopwatch == null)
        {
            throw new ArgumentNullException("stopwatch");
        }
 
        this.repository = repository;
        this.stopwatch = stopwatch;
    }
 
    #region IProductRepository Members
 
    public IEnumerable<Product> SelectTopSellers()
    {
        var timer = this.stopwatch
            .StartMeasuring("SelectTopSellers");
        var topSellers = 
            this.repository.SelectTopSellers();
        timer.StopMeasuring();
        return topSellers;
    }
 
    #endregion
}

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 could be helpful). Here's how we can do it with Pure DI:

IProductRepository repository =
    new PerformanceMeasuringProductRepository(
        new CachingProductRepository(
            new SqlProductRepository(), new Cache()
            ),
        new RealStopwatch()
    );
var vm = new PrécisViewModel(repository);

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.


Comments

Arnis L
That was marvelous post. Never thought about this kind of approach.

Btw, i never figured out if there is anything why service locator isn't anti pattern. :)
2010-04-08 11:27 UTC
Thanks :)

I'm not sure I understand your comment regarding Service Locator. It is an anti-pattern :)

No, seriously, I never expected the entire world to just accept my word as gospel, and there are many people who disagree on this point. Did you have something specific in mind?
2010-04-08 11:37 UTC
I recently listened to a short talk by the MonoTorrent author at FOSDEM 2010. His presentation included an explanation of how (after running into maintenance hell first) he had separated the different concerns in his bittorrent piece picking code by implementing it as a series of decorators.

For me the interesting thing about the talk was that apparently this "separation of concerns" thing had been an important enough discovery for him that it warranted the use of half the presentation time to explain, with the other half being spent talking about the dangers of multi-threading :-)
2010-04-09 07:24 UTC
Kshitij
Love, the blog spot. thanks for showing DI in action.
2010-04-19 00:01 UTC
Totally off-topic comment, but I believe this is the first time I witness C# code with acute accents :) Do you really use accents in your code?
2010-04-26 21:20 UTC
He he, no, normally I don't, but sometimes when writing sample code I like taking advantage of the fact that C# is based on Unicode. Somewhere here, I also have a sample that uses Danish characters (æ, ø or å), but I can't remember which post it was :)
2010-04-26 21:30 UTC
Hey Mark,
A little off-topic, but how'd you implement Cache to force evaluation if it gets passed a Func<IEnumerable<Something>>? Else it will just cache the query.
2010-10-08 22:17 UTC
Yes, you are right. Perhaps it will just cache the query - it actually depends on what the concrete implementation is. It may be an array or List<T>, in which case there is no issue.

However, we could always specialize the implementation of the cache so that if T was IEnumerable, we'd invoke ToList() on it before caching the result.
2010-10-09 07:04 UTC
Geat post, this shows clearly how you can chain functionality without violation OCP and SRP
2011-04-07 08:34 UTC
Tom Stickel
Awesome as usual. Once I drank in the Mark Seemann punch, I'm addicted to following how to do DI properly.
Thanks Mark. Any books from you scheduled for this year or the next?

2012-01-15 19:04 UTC
Thanks, Tom. No new book scheduled right now :)
2012-01-15 19:33 UTC
Alex
Hi Mark!

What if IProductRepository has 15 methods but only one method should be cached?

Or what if I don't need always the cache? So I have a ProductService that needs a IProductRepository. For 5 cases the ProducrtService would need the CachingProductRepository and for the rest the standard ProductRepository?
2012-09-12 11:46 UTC
If you have 15 methods and only one should be cached, you can still cache the one method with a Decorator. The remaining 14 methods on that Decorator can be implemented as pure delegation.

However, if you have this scenario, could it be that the interface violates the Interface Segregation Principle?
2012-09-12 11:56 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Wednesday, 07 April 2010 19:49:11 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!