The entire premise of the Domain Driven Design is based upon evolving a domain model as the cornerstone of the design activity. A domain model consists of domain level abstractions, which builds upon intention revealing interfaces, built out of the Ubiquitous Language. And when we talk about abstractions, we talk about data and the associated behavior. The entire purpose behind DDD is to manage the complexity in the modeling of these abstractions, so that we have a supple design that can be carefully extended by the implementers and easily used by other clients. In the process of extension, the designer needs to ensure that the basic assumptions or behavioral constraints are never violated and the abstractions' published interfaces always honor the basic contractual framework (pre-conditions, post-conditions and invariants). Erik Evans never meant Java interfaces when he talked about intention-revealing-interfaces - what he meant was more in terms of contract or behavior to be modeled with the most appropriate artifact available in the language of implementation.
Are Java interfaces sufficiently intention-revealing ?
The only scope that the designer has to reveal the intention is through the naming of the interface and its participating methods. Unfortunately Java interfaces are not rich enough to model any constraints or aspects that can be associated with the published apis (see here for some similar stuff in C#). Without resorting to some of the non-native techniques, it is never possible to express the basic constraints that must be honored by every implementation of the interface. Let us take an example from the capital market domain :
interface IValueDateCalculator {
Date calculateValueDate(final Date tradeDate)
throws InvalidValueDateException;
}
The above interface is in compliance with all criteria for an intention-revealing-interface. But does it provide all the necessary constraints that an implementor need to be aware of ? How do I specify that the value-date calculated should be a business date after the trade date and must be at least three business dates ahead of the input trade-date ? Pure Java interfaces do not allow me to specify any such criteria. Annotations also cannot be of any help, since annotations on an interface do not get inherited by the implementations.
Make this an abstract class with all constraints and a suitable hook for the implementation :
abstract class ValueDateCalculator {
public final Date calculateValueDate(final Date tradeDate)
throws InvalidValueDateException {
Date valueDate = doCalculateValueDate(tradeDate);
if (DateUtils.before(valueDate, tradeDate) {
throw new InvalidValueDateException("...");
}
if (DateUtils.dateDifference(valueDate, tradeDate) < 3) {
throw new InvalidValueDateException("...");
}
// check other post conditions
}
// hook to be implemented by subclasses
protected abstract Date doCalculateValueDate(final Date tradeDate)
throws InvalidValueDateException;
}
The above model checks all constraints that need to be satisfied once the implementation calculates the value-date through overriding the template method. On the contrary, with pure interfaces (the first model above), in order to honor all constraints, the following alternatives are available :
- Have an abstract class implementing the interface, which will have the constraints enforced. This results in an unnecessary indirection without any value addition to the model. The implementers are supposed to extend the abstract class (which anyway makes the interface redundant), but, hey, you cannot force them. Some adventurous soul may prefer to implement the interface directly, and send all your constraints for a toss!
- Allow multiple implementations to proliferate each having their own versions of constraints implementations - a clear violation of DRY.
- Leave everything to the implementers, document all constraints in Javadoc and hope for the best.
Evolving Your Domain Model
Abstract classes provide an easy evolution of the domain model. The process of domain modeling is iterative and evolutionary. Hence, once you publish your apis, you need to honor their immutability, since all published apis will potentially be used by various clients. Various schools of thought adopt different techniques towards achieving this immutability. Eclipse development team use extension of interfaces (The Extension Object Design Pattern) and evolve their design by naming extended interfaces suffixed by a number - the I*2 pattern of interface evolution. Have a look at this excellent interview with Erich Gamma for details on this scheme of evolution. While effective in some situations where you need to implement multiple inheritance, I am not a big fan of this technique for evolving my domain abstractions - firstly, this technique does not scale and secondly, it requires an
instanceof
check in client code, which is a code-smell, as the gurus say.Once again, to support smooth evolution of your domain apis, you need to back your interfaces with an abstract class implementation and have the implementers program to the abstract class, and not the interface. Then what good is the interface for ?
Are Interfaces Useless in DDD ?
Certainly not. I will use pure interfaces to support the following cases :
- Multiple inheritance, particularly mixin implementations
- SPIs, since they will always have multiple implementations and fairly disjoint ones too. The service layer is one which is a definite candidate for interfaces. This layer needs easy mocking for testability, and interfaces fit this context like a charm.
Some of the proponents of using interfaces claim testability as a criterion for interface based design, because of the ease of mockability. Firstly, I am not sure if domain objects can be tested effectively using mocking. Mocking is most suitable for the services and SPIs and I am a strong supporter of using interfaces towards that end. Even with classes, EasyMock supports mocking using CGLIB and proxies.
Finally ...
I think abstract classes provide a much more complete vehicle for implementation of behavior rich domain abstractions. I prefer to use interfaces for the SPIs and other service layers which tend to have multiple implementations and need easy mocking and for situations where I need multiple inheritance and mixin implementations. I would love to hear what the experts have to say on this ..
11 comments:
Hi Debashih,
You say that the I2 approach doesn't scale, but you fail to provide an alternative and you conveniently leave out the fact that this approach has been used successfully in many very popular API's (among which Eclipse, Win32 and even the JDK), some of which allow to run code that was written more than ten years ago.
To my knowledge, I2 is the only way to correctly address the immutability of your API's and therefore, to guarantee the longevity of your code.
Hi Cedric -
Before going into any debate, let me acknowledge the fact that I am a regular reader of your blog and I enjoy every bit of it. We have also been using TestNG with great success for the last one year or so.
Now to the point that u have made. I have mentioned that I*2 is being used in Eclipse and it does not need a certificate by me to bring home the fact that it has been used successfully. By mentioning that it does not scale, what I meant was that in the face of an evolutionary model, very soon we may reach I*4 or I*5 and the client code getting littered with multiple checks of *instanceof*. In case of Eclipse, I know that api design is being controlled extremely well and with utmost care - it is not so in the run-of-the-mill enterprise projects that get kicked off every now and then. And the alternative ? I think abstract classes provide a better scalability and evolutionability (in the sense that u can plug in a default implementation). This is purely from my personal experience - I am open to suggestions on the contrary as I have mentioned in the post requesting for suggestions from the experts.
Hi Debasish, and thanks for the kind words.
Using concrete classes (even abstract ones) is a worse solution to the problem that the I*2 approach, because it ties you to an implementation, no matter how thin. Before you know it, your clients will be relying on subtle side-effects and undocumented behaviors of your code, and it will make future evolution much harder than if you had used interfaces from the start.
I would also argue that instanceof is only evil when polymorphism could be used in its place, which is not the case when you are trying to have two different versions of an API co-exist in a client. instanceof looks perfectly reasonable to me in this case.
Great topic. You have laid out the issues well - in a manner that is thought provoking. Thank you.
I believe I may extend the discussion on my blog. My immmediate thought comparing IValueDateCalculator to ValueDateCalculator is why not use intention-revealing-exceptions. Your example is adding nothing more than the following definition would:
interface IValueDateCalculator {
Date calculateValueDate(final Date tradeDate)
throws BusinessDateNotBeforeTradeDateException;}
My next thought was that tests reveal intentions of this sort. When reviewing a new package I look at the test assertions.
These are my immediate thoughts. I am curious what you think.
[for cedric :]
Hi Cedric -
My entire post is related to domain level abstractions and not exactly for api design at large. My point is that domain level abstractions *should be* tied to global domain level constraints as well. And we cannot express this using Java pure interfaces. In the example that I posted, the value-date calculated must honor the post-conditions that have been checked in the abstract class. Hence, instead of every implementer of a pure interface, implementing the same constraints (violation of DRY), why not mandate it in the skeleton implementation.
For cases where such constraints are not there, which I find mostly for SPIs and service layer APIs, pure interfaces are great - they allow easy mocking. But for behavior rich domain objects, doesn't it make sense to tie the implementers to the global constraints ?
Your thoughts please ..
[for rjae :]
Hi Rjae -
Intention revealing exceptions are definitely part of intention revealing interfaces, as Erik Evans has said it. And, they also should be coming from the ubiquitous language. In the example that I have posted, InvalidValueDateException is one such artifact.
The strategy that I usually follow is to have the base class InvalidValueDateException in the throws clause of the api and have derived classes like ValueDateLessThanTradeDateException (extending InvalidValueDateException) or ValueDateNotBusinessDateException thrown from within the method for specific cases. All child exceptions that may get thrown are documented with the Javadoc. However, in the example which I posted, my focus was a bit different and hence the exception handling part did not get adequate focus.
But, yes, I agree with u that all exceptions should be intention revealing.
I'd stick with interfaces and complement the API with a set of tests to verify documented required conditions. You'd still be left at the mercy of the implementors, but that's not easy to get out of!
Hi Debasish,
nice to see the discussion continuing on blogs.
I fully agree with Cedric's point:
Using concrete classes (even abstract ones) is a worse solution to the problem that the I*2 approach, because it ties you to an implementation, no matter how thin. Before you know it, your clients will be relying on subtle side-effects and undocumented behaviors of your code, and it will make future evolution much harder than if you had used interfaces from the start.
Other than this, I'd like to say that in any way I don't like the I*2 approach in domain models, because domain model interfaces are not the same as service provider interfaces or alike: domain model interfaces are mostly used/implemented in the context of the same business application, so every change to the published interface can be better supported through refactoring.
Obviously, if domain model interfaces were used in unknown contexts, that would be a problem.
Just my two cents.
Cheers!
Sergio B.
Good post, I agree with almost all of it.
In my view people often talk about using interfaces in the domain when there will only actually be one implementation, to me thats a poor design.
I also don't see the exceptions are part of the domain language, instead I see the associated rules as being part of the domain language. So for me the rule name should become part of the domain language but the exceptions do not have to be, its the rules that matter to me when discussing things with the domain experts.
By the way where can I find Cedrics blog, his blogger profile is not available.
@Colin:
I have to disagree with you on the exception part. My feeling is that exceptions *should* be part of the domain language, along with the rules.
And Cedric's blog is here : http://beust.com/weblog.
I guess it depends on the way you use rules. When we evaluate them we tend to do it for an entire aggregate, so you get 0+ broken rules. In that case if you try to do something that broken rules won't allow we raise a bog standard validation exception and attach the broken rules.
An example blog entry on the topic might be interesting :)
Post a Comment