
Recently, I was responding to a question on the TDD list and found myself writing this:
Although there's only one use of the interface, you could think of it as a way of keeping a clean distinction between the naming of different domains (user and persistence). It makes managing package dependencies easier too.
which is what I've been meaning to say for ages. The reason we're keen on discovering interfaces in our code is that we think they're a good way of expressing concepts in a domain.
If I'm writing a ring tones store (the video store folded last year) and I need to look up, say, the customers that were active last month, I'll ask a SalesLedger object. So, what type is SalesLedger? If it's a class then my marketing module will have a dependency on whatever persistence framework I use. If my SalesLedger class is clean and delegates to some further persistence type then I've only deferred the problem. I'm importing, directly or indirectly, the persistence packages into my domain code. If I do the same with a few other frameworks (printing, display, network, etc.) then I'll have tied down my domain module with third party dependencies. The module dependency chain looks like this:

There are two problems with this, one obvious and one subtle. The obvious point is that I've raised the risk of inflexible code. One day I'll have to rip out a messy chain of dependencies to get to some code I want to change. Now, because I'm another World's Greatest Software Developer, I'll be careful not to embed the dependency too deeply and keep my packages clean and layered, but that's intellectual overhead I could do without. When SalesLedger is an interface, I have a clear distinctions between the domains of marketing and persistence, and I cannot let the abstractions leak through. If I do this for all my external dependencies, I can package up my domain code and use it in all sorts of ways I haven't thought of yet. The top-level Application then becomes a matchmaker, introducing all the modules to each other and helping them get started.

The more subtle issue is that SalesLedger, the interface I use in the body of my code, is in a different domain from the HibernateLedger, a class that implements it (and maybe some other interfaces). The code that uses a SalesLedger is concerned with marketing and payments, the code that uses a HibernateLedger is concerned with setting up connections. There may only be one implementation of an interface, but the two types are addressing different things.
Is this breaking YAGNI? I don't think so (although there are others that do). I'm not adding features I haven't identified a need for yet. Part of the deal with YAGNI is that I make the code as expressive of my current intentions as I can, and I think I'm expressing the needs of the domain code needs more clearly by limiting its dependency to an interface that defines just the services it needs from other parts of the system.
n.b. Gulliver image taken from news.bbc.co.uk
7 comments:
Hey Steve -- I posted on my blog about a technique I am using that is heavily inspired by Mock Objects. Thought you might like to check it out (couldn't find any other way to get in touch) Client First Development
An interesting thought. We tend to write stub servers that we can control directly from the acceptance test code.
Why should SalesLedger have dependence on the persistence framework ? SalesLedger can be modeled as a domain entity with transparent persistence of JPA / Hibernate. You imports will only be JPA specific annotations (javax.persistence.*). If you do not like them either, u can use XML and have a clean domain object.
Cheers.
Good point, I'd prefer the external XML rather than tying my domain code to my DB schema.
That said, at some point I have to get to my EntityManagerFactory, which might have various environment-specific settings, so I'd want to abstract that away. Then I might or might not want to express my searches in terms of *QL, so perhaps that should be hidden behind an API. It's better but not perfect.
So, while I haven't chosen the best example, I think the point still has validity.
I should also point out that I am trying to be provocative here (my world is kinda narrow).
I don't think this is provocative, to me at least. As far as I know most people in DDD will extract an interface from their repositories and put that in the domain model assembly in these situations (though avoiding calling repositories from the domain entities is also a valid approach).
You can then use IoC or a Registry to get the implementation of the repository hooked up to the interface at run time.
My view is in this case you need an interface for the reasons you describe, so whilst I don't like little interfaces that have one implementation for situations like this I'm more than happy to do it.
You seem to take it further and extract high level interfaces to use between domain entities, which is different approach.
What I should have said at the end was:
"You seem to take it further and extract high level interfaces to use between domain entities, which is different approach and is also interesting."
Post a Comment