Dependency Inversion (DIP) - Where I draw the line
I was reading James Kovacs' MSDN Magazine article Tame Your Software Dependencies for More Flexible Apps. I spent some time writing some comments to put in the related blog entry but I thought I should write up my thoughts in a little more detail because I do think that my views on dependency inversion principle (DIP) differ from other peoples.
What Does DIP Give Me
First off what is DIP, if you don't know then go read Robert Martins description and/or do a search on Google because there is lots of stuff out there (including this article by Jeremy Miller).
Testing is often the underlying reason that people push for DIP. That's a big pity because it leaves people thinking that DIP is only useful for testing, which is nonsense.
So lets go back to some of the reasons Robert Martin introduced DIP:
- Coupling Important Logic To Details - High-level modules should not depend on low-level modules, you don't want important business code coupled to implementation details.
- Reuse - You want to be able to reuse your important policy/business logic in different contexts.
So how do we violate DIP, well we can easily do it by making our domain logic depend on implementation details. Put your repositories implementation in the same project as the domain logic and call those repositories from the domain and you've got it (though the repository classes do provide some encapsulation of the details). Likewise coupling your domain logic to a specific logging framework, or maybe to a specific vendors API.
So DIP is great, no doubt, an a sensible principle. However I disagree with some of the forum entries/blog posts and so on about DIP...
Issues I Have
So let me make clear again, I don't have a problem with DIP, in fact I think it is very useful. However I do have an issue with the way people describe DIP because in many cases it differs from my experience, here are the issues I have with some of the writing about it:
- Layering - Robert Martins got something on layering in his post Layers, Levels & DIP. For me the key layer is the domain so I focus more effort on its dependencies because I consider it a "higher" layer, therefore DIP all the way. However I'm not so worried when it comes to the Service layer, why? Well because our service layer is lightweight (as little domain logic as possible) and shallow (not likely to have a deep call stack) so deciding to inject later is an easier change than in the domain (where you might decide you need a service 6 layers deep in a call change). The point I'm making is that DIP relates to coupling between layers but indeed the amount I'm worried about coupling depends on which layer its to and from.
- Mocking - I've blogged about it many times before but I've come to the view that decoupling to allow me to get in Test Doubles is not always improving my design. It depends though, but I think many people assume that extracting all sorts of interfaces to mock makes for a great design but I'm not sure it does.
- Coupling - So I extract an interface from each repository and now my other layers can be nice and decoupled from the repository. Sure, but you have to remember that if you do change the repository it's probably not going to be by creating another subclass (an aggregate is unlikely to have 2 repositories even if you do switch ORM) but instead by modifying the existing repositories interface. In these situations the interface will need to change, so the interface hasn't really helped hidden the change from the dependent layers. Of course the discussion on interfaces is a big one and I am of course a fan of interfaces/ABC's but I think that they work best when you target their usage and take some time to design the interfaces (to suit the clients).
- Flexibility - The argument goes that if every service style class supports an interface then its easy to decorate them e.g. to add auditing. It is. However you have to weight it up, the chances of you needing to decorate all your repositories is unlikely, if you do you may opt for a doing it a different way (e.g. PostSharp), and even if you do decide to do it using interfaces then you can extract those interfaces and inject at that time. Of course if your code has many clients then putting the interfaces in up-front might be a good idea but in general I'm a big fan of extracting interfaces when you need them (in general) just like I'm a big fan of refactoring to patterns instead of just using design patterns.
- Not All Dependencies Are Equal - How worried about a dependency I am is based on where it is going from/to.
- Mixed Up With DI/IoC - Some people love the fact that when they use and IoC container they get to see all the dependencies in the constructor, this sort of argument can be had separately from a discussion of DIP though.
- Inverting Ownership - In Agile Principles Robert Martin makes it clear that a key change is to invert the ownership of the interface, so that the interface is designed specifically for the client(s). Now I've tended to find that my repositories/services are simple enough that they only have one real interface (the one they get from being a concrete class) but we have had cases where we've had them implement interfaces specifically design for their client(s).
- Swapping Implementation - The obvious advantage. However am I really going to remove one repository implementation and plug in another one, maybe one using another ORM. Nope, that'd be a massive change and I couldn't just do it for one repository.
Note when I discuss interfaces above I'm talking about naive interfaces, OrderRepository having an IOrderRepository interface (interface-implementation pair). If you are creating small role interfaces (ISP) and are giving the resulting interfaces more domain meaning (IOrderBook) then you are absolutely doing a good thing. Even better if the interfaces you define are customized for the specific clients. However I think that few, very few, articles on IoC/DI/DIP focus enough on these things.
So What Point Am I Making
Personally I think a lot of talk about DIP/DI/IoC misses the key point, design. Too many examples focus on details like how you configure your container and assume the natural pre-eminence of the interface approach.
I think this is a bit of a mistake, like I say I like interfaces but I am usually skeptical about the ICustomerRepository style approach because in many places I think people use that style without thinking about what real benefits they are getting.
In our case our service layer directly creates repositories/domain service layer when it needs them. This could backfire, for sure, but so far I'm not convinced that its a big mistake and if we find that it was wrong it will be relatively easy to change. Am I violating DIP when my service layer contacts a repository directly, yes. However I'm doing it with my eyes open and (I hope) with a good understanding of the consequences.
My advice would be, before going out and learning about Windsor/Binsor and the rest find out a bit more about DIP and why its useful, that way you can judge for yourself how to proceed.
Agreed!
ReplyDeleteMy take on DIP in regards to testing as its main motivator is in the section "But TDD enforces good OO design idioms, such as IoC! And ummm..." of my article Test Supported Development (TSD) is NOT Test Driven Development (TDD)