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 ..