Justifying Our Designs [IoC/DI/AOP/Auto Mocking Contaner]
There was an interesting thread on the ALT.NET forum called "AMC: Changes to the way we think".
Now I don't use the auto-mocking container so it hasn't changed the way I think but I did want to comment on some of the ideas on the thread.
Should We See All Dependencies In The Constructor?
A lot of people seem to think that seeing a classes dependencies in its constructor is important, the argument being that seeing this tells you a lot about the design of the class.
This is a compelling argument and to some extent I agree with it but it misses one key point, not all dependencies are created equal. A dependency on, for example, a domain service or a repository tells me more about how a class works than a dependency on a logging/dirty tracking service. The first is a meaningful part of the design, the second is just an aspect of the implementation.
You could also say that seeing all a classes dependencies doesn't necessarily tell you much about how it behaves, to know that you need to see its own behavior and the way it uses those dependencies.
I'd also say that this is a case where people argue about the improvements in design when in some cases we are only doing what we are doing to fit in with the implementation constraints of the tools we use. We need to pass the dependencies in so we use constructor injection, to mock we need interfaces (or virtual members) so we end up injecting interfaces. The end result is very decoupled but is it useful decoupling, if we started from scratch and did the simplest thing that can work (YAGNI) would this be the design we'd come up with? Probably not...
AOP
Dependencies from the domain, Ayende indicates that he prefers his domain classes don't depend on non-domain services.
I buy into this too but for things like dirty tracking of domain classes it can get difficult but this is where AOP can prove useful, in those cases you're domain classes might have a run-time dependency on non-domain services but I think this is perfectly accessible. We can also probably use test spies for these sorts of dependencies, which makes for conveniant testing.
Testing
To me the auto-mocking container is a good idea but two things worry me about it.
The first is that it couples your tests to IoC, which when I first read about IoC was seen as a bad solution. I guess you can put up with this though.
The second issue I have with it is that it hides the dependencies that aren't important to the test. It seems like we've exposed the dependencies in the constructor to allow IoC and to allow replacing them in tests. However this makes the tests harder to read so we introduce a component to fix that issue. It just seems like it might be worth taking a step back and re-evaluating before you use the AMC, after doing this you might want to go ahead and use it of course :)
"Bad Designs" Can Work
So our domain classes have few dependencies other than on other domain classes (not on any services outside the domain). Where there are dependencies they are through an interface to the service, you get the service from a Service Locator.
However have layers above this, including a coordination style layer that talks to repositories and the rest.
So how do we get the repositories and infrastructure services into the services in the coordination layer, choices would be:
- Extracted interfaces passed in to the constructor of the service - Pass in an ICustomerRepository (or rename the interface to give it domain meaning).
- Pass concrete classes into the constructor of the service - Pass a CustomerRepository in.
- Make the service methods static but pass in the dependencies.
- Get the required services from a Service Locator.
Which do we do, none. The service methods are all static and when one of these domain coordination services needs a repository/infrastructure service it just creates it.
Its not as decoupled as we could make it but it's clear and simple and the layer in question is quite thin. If we needed to decouple we could without much effort and so I'm quite comfortable with the design we have.
What I'm trying to say is that I sometimes think people take things too far and that sometimes you can couple things safely. Maybe tomorrow you will need to rethink, but maybe not.
This doesn't mean that I don't rate IoC/DI or decoupling in general, I do. However I like to be able to decide for myself how far to take it.
Coupling Code To IoCThis was one of the original suggestions and I don't particularly like it. If you don't want to see the dependencies passed in to the constructor then I'd say you should use a Service Locator (which could in turn call out to a container) or use the hub service style approach.
No comments:
Post a Comment