Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

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.

Customer

A Customer class:

Code Block
public class Customer {
  private Integer id;
  private String name;
    
  public Customer() {}

  public Integer getId() {
      return id;
  }

  public void setId(Integer id) {
      this.id = id;
  }

  public String getName() {
      return name;
  }

  public void setName(String name) {
      this.name = name;
  }
  // business functions
  public void doBusiness() {
      service.serve(this);
  }

  // injected service
  private SomeService service;
  public void setSomeService(SomeService) {
      this.service = service;
  }
}

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.

Decorator

Note

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:

Code Block
class InjectingCustomerDao implements CustomerDao{
  private final CustomerDao dao;
  public Customer load(...){
    Customer customer = dao.load(...);
    inject dependency into customer;
    return customer;
  }
  ...
}

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

Code Block
public interface Injector{
  void inject(Object obj);
}

The InjectingCustomerDao can be implemented as:

Code Block
class InjectingCustomerDao implements CustomerDao{
  private final CustomerDao dao;
  private final Injector injector;
  public Customer load(...){
    Customer customer = dao.load(...);
    injector.inject(customer);
    return customer;
  }
  ...
}

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

Code Block
public interface CustomerFactory{
  Customer createCustomer();
}

Service

Using CustomerFactory, the CustomerService implementation becomes:

Code Block
public class CustomerServiceImpl implements CustomerService {
  public CustomerServiceImpl() {}  
  public void loadAndDoBusiness(Integer id) {
    Customer original = dao.load(id);    
    Customer duplicate = factory.createCustomer();
    duplicate.setId(original.getId() + 1);
    duplicate.setName(original.getName());
    
    original.setName("Michael Walls");
    
    original.doBusiness();
    duplicate.doBusiness();
  }
  
  private final CustomerDao dao;
  private final CustomerFactory factory;
  public CustomerServiceImpl(CustomerDao dao, CustomerFactory factory){
    this.dao = dao;
    this.factory = factory;
  }
}

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:

Code Block
<bean id="someservice" class="SomeService" .../>
<binder id="customer injection" var="customer">
  <bean component="$customer" props="{someService=$someservice}"/>
</binder>
  • The '$' is used to indicate a reference, if you don't already know.

The configuration for a CustomerFactory implementation that injects dependency is:

Code Block
<factory id="customer factory" type="CustomerFactory">
  <bind binder="$customer injection">
    <ctor class="Customer"/>
  </bind>
</factory>
  • 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:

Code Block
<inject id="rich_dao" type="CustomerDao"
    injectee="Customer"
    injection="$customer injection">
    <bean id="raw dao" class="CustomerDaoImpl" .../>
</inject>
  • 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:

Code Block
<!-- injection logic -->
<bean id="someservice" class="SomeService" .../>
<binder id="customer injection" var="customer">
  <bean component="$customer" props="{someService=$someservice}"/>
</binder>

<!-- CustomerFactory implementation -->
<factory id="customer factory" type="CustomerFactory">
  <bind binder="$customer injection">
    <ctor class="Customer"/>
  </bind>
</factory>


<!--The dao that injects dependencies -->
<inject id="rich dao" type="CustomerDao"
    injectee="Customer"
    injection="$customer injection">
    <bean id="raw dao" class="CustomerDaoImpl" .../>
</inject>

<!--CustomerService implementation-->
<ctor id="customer service" class="CustomerServiceImpl"
    args="$rich dao, $customer factory"/>

The Java code you ever need to implement is Customer and CustomerServiceImpl, with aboslutely zero dependency on infrastructure.

Summary

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.