Spring 2.0 vs. the Anemic Domain Model - No Fluff Just Stuff

Spring 2.0 vs. the Anemic Domain Model

Posted by: Craig Walls on December 12, 2005

One of the most interesting aspects (no pun intended) of Spring 2.0 that was discussed this past week at The Spring Experience was the idea of letting Spring configure beans post-instantiation and regardless of how the bean became instantiated. This Spring 2.0 feature helps avoid the Anemic Domain Model" anti-pattern as described by Martin Fowler.

It's very common in Spring to build applications where service objects are injected with DAO objects and use those DAO objects to handle persistence of domain objects. The domain objects themselves, however, are little more than dumb data holders. The problem with this approach is that the interaction between the service object and the DAO object is very procedural. The service object makes one or more calls to the DAO, passing the domain object around like cargo.

Ideally, the domain object would contain behavior to handle its own persistence. If domain objects offered such behavior, then the service object could deal directly with the domain object in a very object-oriented way. Instead of telling a DAO to persist a customer, the service would tell the customer to persist itself. There will still be a DAO, but the domain object will do its own dealing with the DAO, unbeknownst to the service object. In effect, the domain object and the DAO swap positions with relation to the service object.

If the domain object is responsible for dealing with the DAO, then the domain object must have access to the DAO. In Spring, we'd like to do this through dependency injection. But domain objects are typically instantiated outside of Spring (e.g. in Hibernate, iBATIS, or some other persistence mechanism). How can Spring inject a DAO into a domain object when Spring isn't the one instantiating that domain object?

AspectJ to the rescue

If there was any common theme expressed during The Spring Experience last week it was that AspectJ is going to play a huge part in Spring 2.0. Indeed, the addition of Adrian Colyer to the Spring team has triggered a large number of AspectJ-related enhancements. While Spring AOP is nice, AspectJ offers a great deal of potential that cannot be found in proxy-based AOP.

One of the AspectJ-powered enhancements is the inclusion of org.springframework.beans.factory.aspectj.BeanConfigurer. BeanConfigurer is an AspectJ aspect that performs dependency injection on objects after instantiation...even objects that aren't instantiated by Spring. Let's take a look at BeanConfigurer in action.

Imagine an application that (among other things) maintains a database of customers. In such an application, you may have a Customer domain object that looks like this:

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;
  }
}

(NOTE: I've purposefully kept the Customer class simple with only ID and name properties so as not to clutter the example with unnecessary noise. In a real-world application, this class would likely have many more properties.)

Notice that the Customer class doesn't have any real functionality--It's merely a data holder. This is what the Customer class might look like in a pre-2.0 Spring application. It's assumed that instances of Customer will be passed around to a CustomerDao implementation that will handle the persistence of the object.

But in Spring 2.0, we can give the Customer a bit more power:

import org.springframework.beans.factory.aspectj.SpringConfigured;

@SpringConfigured("customer")
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 save() {
      dao.save(this);
  }

  // injected DAO
  private CustomerDao dao;
  public void setDao(CustomerDao dao) {
      this.dao = dao;
  }
}

The first thing you'll notice is the use of a @SpringConfigured annotation. @SpringConfigured tells the BeanConfigurer aspect that you wish for Spring to perform dependency injection on instances of this class post-instantiation. The value passed to @SpringConfigured is the name of a <bean> in the Spring application context that will serve as a template for injection.

The dependency injection that will be done to instances of Customer is a CustomerDao. Note that Customer also has a new save() method that uses the injected CustomerDao to save itself.

Now let's look at the XML for the Spring application context:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop.xsd">

  <aop:spring-configured/>
  
  <bean id="customerDao"
      class="com.habuma.account.CustomerDaoImpl"/>

  <bean id="customer"
      class="com.habuma.account.Customer"
      lazy-init="true">
    <property name="dao" ref="customerDao" />
  </bean>  
</beans>

The first thing about this XML file that may strike you odd is that instead of using a DTD, XML Schemas are being used to validate the XML. There are a handful of good reasons for going with schemas instead of DTD, but for the purposes this discussion, the main reason is that the "http://www.springframework.org/schema/aop" schema gives us the <aop:spring-configured/> XML element. This element is just one example of some of the XML simplifications coming in Spring 2.0. Using <aop:spring-configured/> is roughly equivalent to the following chunk of XML:

<bean id="beanConfigurer" 
    class="org.springframework.beans.factory.aspectj.BeanConfigurer"
    factory-method="aspectOf" />

Aside from being more terse, the purpose of <aop:spring-configured/> is a bit more clear. In short, it loads the BeanConfigurer aspect. Once loaded, BeanConfigurer will keep an eye out for instantiations of any class that is annotated with @SpringConfigured and inject them as prescribed in the application context.

In the case of Customer, the @SpringConfigured annotation tells BeanConfigurer to use the bean whose id is "customer" as its guide for injecting new instances of Customer. As you can see, the "customer" bean is configured to be injected with an instance of CustomerDaoImpl.

You'll also note that the "customer" bean is configured with lazy-init="true". Since Spring will only use this bean as a template, setting lazy-init to "true" tells Spring to not bother creating an instance when the Spring container is loaded.

To try all of this out, let's create a service object:

public class CustomerServiceImpl implements CustomerService {
  public CustomerServiceImpl() {}
  
  public void copyAndTweakCustomer(Integer id) {
    Customer original = dao.load(id);
    
    Customer duplicate = new Customer();
    duplicate.setId(original.getId() + 1);
    duplicate.setName(original.getName());
    
    original.setName("Michael Walls");
    
    original.save();
    duplicate.save();
  }
  
  private CustomerDao dao;
  public void setDao(CustomerDao dao) {
      this.dao = dao;
  }
}

The copyAndTweakCustomer is a rather contrived method that loads a customer given an ID, makes a copy of the customer, changes the name of the customer, then saves both the original and the duplicate customer object. Take note that the code that makes up copyAndTweakCustomer() is very OO. For comparison, consider what this may have looked like if Customer was more anemic:

  public void copyAndTweakCustomer(Integer id) {
    Customer original = dao.load(id);

    Customer duplicate = new Customer();
    duplicate.setId(original.getId() + 1);
    duplicate.setName(original.getName());
    
    original.setName("Michael Walls");
    
    dao.save(original);
    dao.save(duplicate);
  }

The code is only slightly different. But the difference is important. In an anemic domain model, the service layer code is more procedural, whereas in a strong domain model the service layer code is more object oriented.

You'll notice that the service object is also injected with a CustomerDao. This is so that it can load a previously-persisted Customer. This illustrates an important point: Just because the domain object is now primarily responsible for its own persistence, that does not mean that service objects can't also access the DAO to load previously-persisted instances.

Speaking of which, we also need to add the CustomerServiceImpl configuration to the Spring configuration:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

  <aop:spring-configured/>
  
  <bean id="customerDao"
      class="com.habuma.account.CustomerDaoImpl"/>

  <bean id="customer"
      class="com.habuma.account.Customer"
      lazy-init="true">
    <property name="dao" ref="customerDao" />
  </bean>  

  <bean id="customerService"
      class="com.habuma.account.CustomerServiceImpl">
    <property name="dao" ref="customerDao" />
  </bean>
</beans>

The actual implementation of CustomerDaoImpl is up to you. It could be Hibernate-based, iBATIS-based, OJB-based, TopLink-based, or (in Spring 2.0), it could be based on the Java Persistance API. For purposes of this example, I've implemented a dummy implementation of CustomerDao that looks like this:

public class CustomerDaoImpl implements CustomerDao {
  public CustomerDaoImpl() {}
  
  public void save(Customer customer) {
    System.out.println("SAVING A CUSTOMER:");
    System.out.println("  customer.id   = " + customer.getId());
    System.out.println("  customer.name = " + customer.getName());
  }
  
  public Customer load(Integer id) {
    System.out.println("LOADING A CUSTOMER WITH ID: " + id);
    Customer customer = new Customer();
    customer.setId(id);
    customer.setName("Craig Walls");
    return customer;
  }
}

Now let's give it a try. Create a class with a main() method that looks like this:

  public static void main(String[] args) {
    ApplicationContext ctx = 
        new ClassPathXmlApplicationContext("com/habuma/account/applicationContext.xml");
    
    CustomerService service = (CustomerService) ctx.getBean("customerService");
    
    service.copyAndTweakCustomer(new Integer(5));
  }

Using my dummy CustomerDaoImpl, you should see the following output on STDOUT:

LOADING A CUSTOMER WITH ID: 5
SAVING A CUSTOMER:
  customer.id   = 5
  customer.name = Michael Walls
SAVING A CUSTOMER:
  customer.id   = 6
  customer.name = Craig Walls

@SpringConfigured and <aop:spring-configured/> together represent just one of several awesome improvements being made in Spring 2.0. In this article, you also saw a hint of some XML simplification that's coming. And Spring 2.0 will also come with the 1.0 release of Spring Web Flow, support for the Java Persistence API, and many more goodies to watch out for. Keep an eye on this blog. As I have time and as I try out the new Spring 2.0 features, I'll tell you about them here.

What if it doesn't work?

The use of BeanConfigurer assumes several things:

  • The use of Spring 2.0 (M1 will be out any day now, but you can download a fairly recent version from the nightly builds). You'll need spring.jar and commons-logging.jar in the classpath.
  • It requires Java 5 due to the use of the @SpringConfigured annotation.
  • The code should be compiled with AspectJ's "ajc" compiler. This is so that AspectJ gets a chance to weave BeanConfigurer into Customer. Once compiled, it can be run using any Java 5 JVM.
Craig Walls

About Craig Walls

Craig Walls is a Principal Engineer, Java Champion, Alexa Champion, and the author of Spring AI in Action, Spring in Action, and Build Talking Apps. He's a zealous promoter of the Spring Framework, speaking frequently at local user groups and conferences and writing about Spring. When he's not slinging code, Craig is planning his next trip to Disney World or Disneyland and spending as much time as he can with his wife, two daughters, 1 bird and 2 dogs.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »