Sunday, July 02, 2006

Spring 2.0 AOP - Spruce Up Your Domain Model

Just started playing around with Spring 2.0 and poke at some of the new features over the weekend. The coolest dude of them looked like the ability to attach post-instantiation processors to beans which have NOT been instantiated by the Spring container. This means a lot to me when I started thinking about how this feature can add value to the current scheme of things in a POJO based Spring configured application.

Have a look at the following domain class from our current application :

// models a security trade
public class Trade {
  // state
  // getters and setters
  // domain behavior
}

A typical domain object is instantiated in the application either from the ORM layer (read Hibernate through persistence services) or by the user using factories. It is never instantiated by the Spring container. Hence there is no way that we can use Spring DI to inject services into the domain model. Any sort of service injection that is done will be through hardwired code :

public class Trade {
  // ...
  public BigDecimal calculateAccruedInterest(...) {
    BigDecimal interest =
      InterestRateDao.getInstance().find(...);
    // ...
  }
  // ...
}

The above domain class now has a hardwired dependency on the class InterestRateDao, which brings in all sorts of unwanted side-effects :


  1. Domain layer should be persistence agnostic

  2. The domain classes should be unit-testable without the service layer

  3. Proliferation of business logic in the controllers



Let me explain a bit on the third point ..

Since I cannot have transparent DAO injection in my domain model, I cannot have my calculateAccruedInterest() method with my domain object. The inevitable impact will be to move the logic down to the controller, the lifecycle which can be configured using Spring. Now I have the controller class which computes the accrued interest of the Trade once the object has been instantiated by the persistence layer. Result ? My domain logic has started infiltrating into the controller layer which, ideally should be a facade only and strictly a *thin* glue between the domain layer and the presentation layer.

// Controller class for trade entry use case
public class TradeService {
  // ...
  public void enterTrade(...) {
    Trade tr = TradeFactory.create(...);
    // accrued interest needs to be calculated only for bond trades
    if (tr.isBondTrade()) {
      tr.setAccruedInterest(
        AccruedInterestCalculator.getInstance()
        .calculateAccruedInterest(tr));
    }
  }
  // ...
}

Design Smell !! The domain model becomes anemic and the controller layer becomes fleshy.

Enter Spring 2.0

The new AOP extensions in Spring 2.0 allow dependency injection of any object even if it has been created outside the control of the container. Our domain objects fit nicely in this category. Service objects can now be injected into domain objects so that the domain model can be enriched with domain behavior ensuring proper separation of concerns across the layers of the application architecture. The enriched domain behavior can now interact with the domain state in a more object-oriented way than the erstwhile anemic model.

Spring 2.0 offers annotation driven aspects towards this end as the most recommended approach towards dependency injection into the domain model. Let us see how the Trade class changes :

@Configurable("trade")
public class Trade {
  // ...
  public BigDecimal calculateAccruedInterest(...) {
    BigDecimal interest =
      dao.find(...);
    // ...
  }
  // ...

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

And the usual stuff in the XML for application context :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
...>

  <aop:spring-configured/>

  <bean id="interestRateDao"
    class="com.xxx.dao.InterestRateDaoImpl"/>

  <bean id="trade"
    class="com.xxx.Trade"
    lazy-init="true">
    <property name="dao" ref="interestRateDao" />
  </bean>
</beans>


Getaways


  • The anemic domain model is history. Fine grained DI using AOP helps domain model regain smart behavior.

  • No compromise on unit testability. For the above annotation to kick in, the annotated types must be woven with the AspectJ weaver either through a build-time ant or maven task or through load-time weaving. Do away with the AspectJ weaving and replace the property references by mock objects in XML and fire to test drive your domain model.

  • All crosscutting infrastructure services can be injected transparently into the domain model from s single point of contact.

  • In case you are allergic towards annotation (can't believe many people are!), you may also use AnnotationBeanConfigurerAspect in spring-aspects.jar (distributed with Spring) to implement the same behavior.

  • Service Objects, Factories and Repositories are typical examples of artifacts that can be injected into the domain model.

22 comments:

Anonymous said...

I'm on of those who dislike annotations, especially if they seem superfluous as in your example. Is the following also supported?

// no annotation
public class Trade {
}

<bean class="com.xxx.Trade" lazy-init="true">
<property name="dao" ref="interestRateDao" />
</bean>

Thanks

Sven

Unknown said...

Annotation is just one way for identifying the injection. The other choice is to write an AspectJ pointcut expression, which is as well offerred by Spring 2.0. If u do not like annotations, then u can very well use an abstract aspect to provide your own pointcut definitions. Spring 2.0 comes bundled with an aspect jar (from AspectJ), spring-aspects.jar, which provides a base class AbstractBeanConfigurerAspect. Using this, u you can write an aspect to configure all instances of objects defined in the domain model using prototypical bean definitions that match the fully-qualified class names. Please refer to Section 7.7.2 of the Spring Reference Documentation for an example.

The important point to note is that the snippet that u have supported without the annotation works *only if* the bean Trade has been instantiated by Spring container. In the case that I discuss in the entry, the beans have been instantiated *not* by Spring IoC, but in some other way (typically either from the persistence layer or by the user using "new"). But still we want to inject dependencies using Spring DI. Hence the annotation in the original example is NOT superfluous. But, as I mentioned, there is a work around as well, if u want to avoid annotations. Besides the personal choice of avoiding annotations, introducing framework annotations into core business objects may be considered a questionable practice in general, since it creates an unnecessary dependency at the domain level. But Spring, as usual, give su options !!

Anonymous said...

@Configurable will only get you so far. Since your DAOs become part of your object graph this is very likely to cause trouble when you're serializing your objects.

You can make DAO fields transient but there's no convenient hook for injection on de-serialization. A better approach is to let getters or fields on you domain classes return beans from the ApplicationContext using AspectJ.

Anonymous said...

I think this way of using Spring can leverage the programmer to use 3rd party software that does not have a spring-integration module: you can use easily a 3rd party software that is not integrable with Spring.

I dislike annotations too, but, I must admit, the @annotation technology is very powerfull!
You can implement interesting funcitonalities using @annotations - Aspectj - Spring2 togheter.
Bye.
Michele

Anonymous said...

I think this way of using Spring can leverage the programmer to use 3rd party software that does not have a spring-integration module: you can use easily a 3rd party software that is not integrable with Spring.

I dislike annotations too, but, I must admit, the @annotation technology is very powerfull!
You can implement interesting funcitonalities using @annotations - Aspectj - Spring2 togheter.
Bye.
Michele

Anonymous said...

I think this is a great feature and, with this way, we can get a domain object to handle its own persistence.
But, what happens if we don't use DTO's and send a Trader object to a remote client somewhere out there and that client calls calculateAccruedInterest?
If calculateAccruedInterest does some DB-related stuff then this will fail.
Is it possible that we can get a lot of methods in a domain object that remote clients aren't able to use?

Cheers!

Anonymous said...

PeeWee: That's exactly my point.

I think this calls for a different kind of architecture design: some objects can be sent to a remote client, others must use DTOs. And if they use DTOs, you will need some kind of service layer to isolate them from the domain model, and that brings us again to the beginning back to the transaction script pattern in the architecture (as Martin Fowler calls it)

Juan

Anonymous said...

you mentioned domain objects are never instantiated by the container, but for spring mvc, you can associate a command object which could be just a domain object associated with the controller

Unknown said...

[juan, peewee]: regarding DTOs

I hope I am not opening another can of worms. But can we totally discard DTOs from the architecture ? Particularly when the presentation layer and business/domain layers are NOT collocated, DTOs are a very much recommended choice, instead of serializing/deserializing big object graphs. But not all domain objects need to be have DTOs. We can be selective on this approach.

Cheers.

boz said...

The blog says domain objects are got either through a persistence service or factories. Whatever has those things can get them via Spring IoC. If so, then they can be designed to inject services into the domain objects before hending them off.... Right?

It doesn't seem like we need aspects unless we get domain objects through "new". Or am I missing something?

boz said...

The blog says domain objects are got either through a persistence service or factories. Whatever has those things can get them via Spring IoC. If so, then they can be designed to inject services into the domain objects before hending them off.... Right?

It doesn't seem like we need aspects unless we get domain objects through "new". Or am I missing something?

Unknown said...

[Christofer Jennings] .. domain objects are got either through a persistence service or factories. Whatever has those things can get them via Spring IoC.

Domain objects which are instantiated by custom factories or from the persistence layer (like Hibernate services) are NOT created by Spring IoC. Same is the case with objects instantiated by "new". So long Spring did not allow to inject services into objects which have NOT been created by Spring. The new feature in 2.0 allows that - u can now inject services in domain objects which have NOT been created by the Spring IoC.

Hope this helps.

Mark said...

Debasish (or others) - We have been trying to use Spring 2.0 to inject domain objects, but we keep getting errors.

Can you point me to a URL that explains the steps in detail? We've been through the Spring docs already.

TIA, Mark

Unknown said...

[mark] : but we keep getting errors

I think the best way to get this solved is by posting the problem along with errors in the Spring Forum. Lots of cooperative Spring developers will help you.

Anonymous said...

Sven,

You can achieve this injection without annotation. Spring provides AutoWireCapableBeanFactory which can inject properties on beans that are not committed to the container.

//No annotation
public class Trade{
}

<bean name="TradeBean" class="com.xxx.Trade" lazy-init="true">
<property name="dao" ref="interestRateDao" />
</bean>


Your controller, responsible for initiating Application will create a AutowireCapableBeanFactory or its ApplicationContext peer on the xml.

One call after that will do the trick.

//Instantiate
Trade trd = new Trade()/factory.create()/whatever...

//Inject property from context
factory.applyPropertyValues(trd, "TradeBean");

Thanks,
Rudra

Anonymous said...

If you get errors, make sure you are using the AspectJ LTW agent:

-javaagent:lib/aspectjweaver.jar

Whole explanation here: http://www.aspectprogrammer.org/blogs/adrian/2006/02/a_practical_gui_2.html

Anonymous said...

does the techique also work if the domain object is backed by hibernate?

Specifically, when hibernate retrieves a domain object, hibernate creates an empty instance and applies its own magic (possibly create a proxy object if needed). Will the AspectJ-based injection kick in for those hibernate cglib-enhanced domain objects?

Unknown said...

does the techique also work if the domain object is backed by hibernate?

Yes, it works. Before the AJ solution, Spring offered org.springframework.orm.hibernate.
support.DependencyInjectionInterceptorFactoryBean as the only means to inject dependency into Hibernate backed objects. But this injection did not work for beans instantiated manually and persisted through session.save() (typical integration testing scenarios). The solution that I mentioned in the blog works seamlessly for DIing into any object NOT instantiated through the Spring container.

HTH.

Anonymous said...

Yes.. finally it works for me.

Thanks for sharing this info.

Unknown said...

This is really a vey cool feature.

Anonymous said...

These comments have been invaluable to me as is this whole site. I thank you for your comment.

Mert Nuhoglu said...

I wonder what is the reason behind thak statement:
"Since I cannot have transparent DAO injection in my domain model, I cannot have my calculateAccruedInterest() method with my domain object"
To me it looks like that we can implement calculateAccruedInterest() method in the domain object even though no transparent DAO configuration were present.
Thanks, Mert