Well, that's great for a starter! Approaching the entree, we find things moving south when various forums in TSS and Spring, Yahoo groups and individual blogs start discussing about domain models going anemic through rich service and presentation layers. Many people have suggested using the DTOs to force a separation between the domain layer and the presentation layer and getting rid of the dreaded LazyInitializationException from Hibernate-backed persistence layer. DTOs create an extra level of indirection to ensure the separation and form a valid architectural paradigm for use cases of distribution and serialization of deep nested object graphs. For others, where all layers are colocated and possibly replicated using clustering, we should always try to maximize the reachability of the domain model.
Domain Model is Crosscutting
The domain model is built upon the layers below it and serves all the layers above it. The domain layer itself encapsulates the business logic and provides fine grained behavioral POJOs to the service layer. The service layer builds up coarse-grained services out of the POJOs exposed by the domain layer and serves the presentation layer above. Hence the POJOs of the domain layer crosscuts all layers above it although exposing different views to each of them. For a particular domain entity, the interface that it publishes within the domain layer may be different from the interface that it exposes to the service or web layer. Hence we need to ensure that the model enforces this multiplicity of interfaces and prevents any implementation specific artifact leaking out of the domain layer.
Let us have a look at a real life example ..
A Domain POJO Exposing Implementation
A domain level POJO named
Order
, which contains a List
of Items ..package org.dg.domain;
import java.util.List;
public class Order {
private int orderNo;
private String description;
private List<Item> lineItems;
public Order(int orderNo, String description) {
this.orderNo = orderNo;
this.description = description;
}
/**
* @return Returns the description.
*/
public String getDescription() {
return description;
}
/**
* @param description The description to set.
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @return Returns the orderNo.
*/
public int getOrderNo() {
return orderNo;
}
/**
* @param orderNo The orderNo to set.
*/
public void setOrderNo(int orderNo) {
this.orderNo = orderNo;
}
/**
* @return Returns the lineItems.
*/
public List<Item> getLineItems() {
return lineItems;
}
/**
* @param lineItems The lineItems to set.
*/
public void setLineItems(List<Item> lineItems) {
this.lineItems = lineItems;
}
public float evaluate( .. ) {
}
}
This is a typical implementation of a domain POJO containing all getters and setters and business logic methods like evaluate(..). As it should be, it resides in the domain package and happily exposes all its implementation through public methods.
Leaky Model Symptom #1 : Direct Instantiation
The first sign of a leaky domain model is unrestricted access to class constructor thereby encouraging uncontrolled instantiations all over. Plug it in, make the constructor
protected
and have a factory class take care of all instantiations.public class Order {
// same as above
protected Order(int orderNo, String description) {
..
}
..
}
package org.dg.domain;
public class OrderFactory {
public Order createOrder(int orderNo, String description) {
return new Order(orderNo, description);
}
}
If u feel the necessity, you can also sneak in a development aspect preventing direct instantiation even within the same package.
package org.dg.domain;
public aspect FlagNonFactoryCreation {
declare error
: call(Order+.new(..))
&& !within(OrderFactory+)
: "OnlyOrderFactory can create Order";
}
Leaky Model Symptom #2 : Exposed Implementation
With the above implementation, we have the complete
Order
object exposed to all layers, even though we could control its instantiation through a factory. The services layer and the presentation layer can manipulate the domain model through exposed setters - a definite smell in the design and one of the major forces that have forced architects and designers to think in terms of the DTO paradigm.Ideally we would like to have a restricted view of the
Order
abstraction within the web layer where users can access only a limited set of contracts that will be required to build coarse-grained service objects and presentation models. All implementation methods and anemic setters that directly manipulate the domain model without going through fluent interfaces need to be protected from being exposed. This plugs the leak and keeps the implementation artifacts locked within the domain model only. Here is a technique that achieves this by defining the restricted interface and weaving it dynamically using inter-type declarations of AOP ..// Restricted interface for the web layer
package org.dg.domain;
import java.util.List;
public interface IOrder {
int getOrderNo();
String getDescription();
List<Item> getLineItems();
float evaluate( .. )
void addLineItem(Item item); // new method
}
// aspect preventing leak of the domain within the web layer
package org.dg.domain;
public aspect FlagOrderInWebLayer {
pointcut accessOrder(): call(public * Order.* (..))
|| call(public Order.new(..));
pointcut withinWeb( ) : within(org.dg.web.*);
declare error
: withinWeb() && accessOrder()
: "cannot access Order within web layer";
}
Note that all setters have been hidden in
IOrder
interface and an extra contract has been exposed to add an Item
to an existing Order
(addLineItem(..)
). This makes the interface fluent and closer to the domain user, since he will typically be adding one item at a time to an Order
.Finally, weave in the
IOrder
interface dynamically through inter-type declarations of AspectJ, when exposing the Order
abstraction to the layers above. In fact every layer to which the full abstraction of Order
has been restricted can make use of this new interface.package org.dg.domain;
public aspect RestrictOrder {
declare parents: Order implements IOrder;
public void Order.addLineItem(Item item) {
this.getLineItems().add(item);
}
}
Hence, the web layer does not get access to the full implementation of
Order
and has to work with the restricted contract of IOrder
, thereby preventing the leaky domain model antipattern. Tailpiece
Now that we have agreed to plug in the holes of the leaky domain model, how should we decide upon the composition of the published restricted interface ?
I find some dichotomy here. The published interface should, on one hand, be restrictive, in the sense that it should hide the implementation contracts from the client (the web layer, in the above case). While, on the other hand, the published interface has to be humane and should follow the principles of Intention-Revealing Interfaces. The interface should add convenience methods targetted to the client, which will help him use the object more effectively and conforming to the Ubiquitous Language.