Rich Domain Injection stands for dependency injection into domain objects that are not instantiated by the container.
Such injection is useful to avoid "Anemic Domain Model" that's considered "Anti-pattern" by Martin Fowler, because in order for a domain object to be "rich", it is sometimes necessary to have some dependencies to other services or objects.
Spring 2.0 gives a solution which requires Spring specific annotation and AspectJ pre-compilation.
The Nuts' solution requires none. Except the xml configuration, no Java code is dependent on the container API.
I'll use Craig Wall's example for demonstration. To avoid arguments on "persistence is not part of domain" topic, I changed the example a little bit so that it uses a general Service object.
A Customer class:
Note, there's absolutely no indication of which infrastructure is to be used to inject this dependency, therefore no lock-in to any container product.
Note, this is just to demonstrate how things work. The final solution implements the decorator for you if you are not a fan of DIY.
The idea is to decorate the CustomerDao interface (Yes, we require the CustomerDao be an interface because we do not want to rely on cglib.) so that it automatically injects dependency.
The pseudo code of CustomerDao decorator class should look like:
We have yet to implement the "inject dependency into customer" logic. And it is still object oriented. We abstract the notion with an interface. (As we just said, the final solution does not require you to worry about this interface. It is hidden under the hood.)
The InjectingCustomerDao can be implemented as:
Factory instead of "new"
Since this solution does not use AOP, constructor of Customer is not intercepted. Hence use of "new Customer()" will not get the dependencies injected.
To get around that, we recommend using a CustomerFactory interface instead of the straight "new".
Using CustomerFactory, the CustomerService implementation becomes:
Configure it using Nuts
The problem is now reduced to an implementation of "Injector". Since CustomerDao is configured in xml, CustomerFactory is configured in xml, CustomerServiceImpl is also congifured in xml, we have all the control that we need.
The configuration for the injection logic is expressed using the <binder> tag:
- The '$' is used to indicate a reference, if you don't already know.
The configuration for a CustomerFactory implementation that injects dependency is:
- The <bind> tag calls the constructor of Customer and then applies the "customer injection" to the Customer object.
- The <factory> tag creates a dynamic proxy that implements CustomerFactory interface. When "createCustomer()" is called, the <bind> tag is invoked, which invokes the constructor of Customer and applies the injection logic.
The configuration for CustomerDao also uses dynamic proxy, therefore no explicit implementation of CustomerDao decorator is necessary:
- The <inject> tag creates a dynamic proxy of CustomerDao that delegates the call to the "raw dao". Upon return, the "injectee" attribute asks for dependency injection on objects of type "Customer" only using the dependency injection logic described by the "customer injection".
- "type" specifies the target CustomerDao interface.
- "injectee" is the return type that accepts dependency injection.
- "injection" specifies the injection logic.
Put everthing together, the configuration files looks like:
The Java code you ever need to implement is Customer and CustomerServiceImpl, with aboslutely zero dependency on infrastructure.
With Spring 2.0, we get the convenience to use "new Customer()" directly in the code. Dependency injection is auto-magically handled by AspectJ. The price to pay is that we have to use spring specific annotation in the domain object. Plus, the code can only work within an AOP framework because "new Customer().doBusiness()" is a plain bug without AOP semantics in place to sneak in logic.
With Nuts, No annotation is required. The code is purely object oriented without a need of AOP. If I like, I can always hand-write the CustomerDao decorator to inject dependencies myself. This makes the code portable and easy to unit-test. The price to pay is that we have to use a CustomerFactory to create Customer objects. "new Customer()" doesn't bring the dependencies we want.