Tuesday, December 26, 2006

Domain Driven Design : Control Domain Object Lifetimes using Spring Custom Scoped Beans

In his celebrated book on Domain Driven Design, Eric Evans mentions that one of the biggest challenges of maintaining the integrity of a domain model is managing the lifecycle of a domain object. He states
But other objects have longer lives, not all of which are spent in active memory. They have complex interdependencies with other objects. They go through changes of state to which invariants apply. Managing these objects presents challenges that can easily derail an attempt at MODEL-DRIVEN DESIGN.

Two points immediately stand out from the above, when we think of modeling a system based on principles of DDD :

  1. Domain object lifecycles need to be managed

  2. Lifecycle management needs to be decoupled from mainstream business logic


Both of the above issues can be addressed using an IoC container like Spring. And the new custom scopes of Spring 2.0 give us the real steroid towards declarative lifecycle management of domain objects.

Custom Scopes in Spring 2.0

Till 2.0, Spring provided only two levels of granularity at which you could declare your beans -

  1. Singleton, which scopes a single bean definition to a single object instance per Spring IoC container and

  2. Prototype, which scopes a single bean definition to any number of object instances. The prototype scope results in creation of a new bean instance every time a request for that specific bean is made.


Hence any other intermediate granularity of bean lifecycles had to be managed explicitly by the application itself. Spring 2.0 comes with three additional levels of bean scopes out of the box, as well as the framework to create your custom scope that suits your application needs. The ones that come out of the box are request, session and global session, which are described in detail in the Spring Reference documentation.

In this blog post, I will elaborate how the domain model can be enriched by defining beans at application defined custom scopes. I will end the post with an example of how declaring the scope as configuration, results in declarative lifecycle management of a domain object, irrespective of the layer to which the object belongs.

Scope your Domain Objects at Business Level Granularity

Prior to 2.0, non-singleton beans could only be defined with a lifecycle that creates a new instance on every access - aka the prototype scope. For beans not created by Spring, there used to be techniques like Field Level Injection and Service Location Strategy which achieves the same effect as prototype beans.

Let us consider a real life example from the financial domain, specifically, a back office system for Capital Market Trading and Settlement. We have an example of a BasketTrade, which is, essentially a collection of Trades matching some criteria and need to be processed together. No surprises here, we model a BasketTrade as :


public class BasketTrade {
  private List<Trade> trades = new ArrayList<Trade>();
  // ..
  // ..
}



The point to note is that the abstraction BasketTrade is just a virtual container for the collection of trades and has been created for the convenience of atomic processing of the underlying Trade objects. It is typically the root of an Aggregate, as Eric defines in his DDD book, which ceases to exist as soon as the processing is complete, e.g. all component trades are committed to the Repository.

In other words, we can define a custom scope (say, basket-scope), which defines the lifecycle of a BasketTrade bean. Typically, the application provides a BasketingService, which can be modeled as a Singleton, and can contain a BasketTrade as a scoped bean within it, injected by the IoC container.


public class BasketingService {
  private BasketTrade basket;

  public void addToBasket(Trade trade) {
    getBasket().add(trade);
  }

  public void setBasket(BasketTrade basket) {
    this.basket = basket;
  }

  public BasketTrade getBasket() {
    return basket;
  }
}



We have two collaborating beans with different lifecycles, which can be wired up with the custom scope definitions as part of the configuration. And Spring 2.0 offers ScopedProxyFactoryBean for this purpose, which offers convenient proxy factory bean for scoped objects. Here we have the xml, which wires the domain objects with custom life cycles :


<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
  <property name="scopes">
   <map>
    <entry key="basket"><bean class="org.dg.biz.trd.BasketScope"/></entry>
   </map>
  </property>
</bean>

<bean id="basketTradeTarget"
  class="org.dg.biz.trd.BasketTrade" scope="basket" lazy-init="true">
</bean>

<bean id="basketTradeProxy"
  class="org.springframework.aop.scope.ScopedProxyFactoryBean">
   <property name="targetBeanName">
    <value>basketTradeTarget</value>
   </property>
</bean>

<bean id="basketingService" class="org.dg.biz.trd.BasketingService">
  <property name="basket" ref="basketTradeProxy"/>
</bean>




Defining the BasketScope

The following is a very naive implementation of the custom basket scope. This is just for demonstrating the power of custom scopes in controlling the lifecycles of domain objects.


public class BasketScope implements Scope {
  private static final Map scope = new ConcurrentHashMap();

  public String getConversationId() {
    return null;
  }

  public Object get(String name, ObjectFactory objectFactory) {
    Object obj = scope.get(name);
    if (obj == null) {
      obj = objectFactory.getObject();
      scope.put(name, obj);
    }
    return obj;
  }

  public Object remove(String name) {
    return scope.remove(name);
  }

  public void registerDestructionCallback(String string, Runnable runnable) {
    // register any custom callback
  }
}




Removing Objects with Custom Scope

This is an area which is not very clear from Spring Reference documentation. No problem, the helpful Spring community was prompt enough to give enough support to clarify all my confusions (see this thread).

For the out-of-the-box implementations of request and session scopes, the lifetime of the scoped bean ends automatically with the end of the request or session - and one can implement HttpSessionBindingListener to plug in custom destruction callback. Have a look at the implementation of DestructionCallbackBindingListener in class org.springframework.web.context.request.ServletRequestAttributes :


private static class DestructionCallbackBindingListener
    implements HttpSessionBindingListener {

  private final Runnable destructionCallback;

  public DestructionCallbackBindingListener(Runnable destructionCallback) {
    this.destructionCallback = destructionCallback;
  }

  public void valueBound(HttpSessionBindingEvent event) {
  }

  public void valueUnbound(HttpSessionBindingEvent event) {
    this.destructionCallback.run();
  }
}



For a custom scope, the simplest way will be to invoke the object removal manually in the workflow. For the above example with BasketTrade, the destruction code looks like :


ScopedObject so = (ScopedObject) basketingService.getBasket();
so.removeFromScope();



Note that proxies returned by ScopedProxyFactoryBean implement the ScopedObject interface, which allows removing the corresponding object from the scope, seamlessly creating a new instance in the scope on next access.

And now on to a neat trick. We can encapsulate the invocation of the destruction callback into a Seam-style annotation marking the end of the conversation scope. In the above code for BasketingService, suppose we would like to end the scope of the basket after a commit to the database of all constituent trades - we have a method commit(), which after database commit will mark the end of the lifetime of the current basket. We mark this declaratively using the @End annotation.


public class BasketingService {
  // as above

  @End
  public BasketTrade commit() {
    // database commit logic
    return basket;
  }
}



Finally the implementation of @End using the usual Spring AOP magic ..


@Aspect
public class DestroyScope {

  @AfterReturning(
    pointcut="@annotation(org.dg.biz.trd.End)",
    returning="retVal")
  public void doDestroy(Object retVal) {
    ScopedObject so = (ScopedObject) retVal;
    so.removeFromScope();
  }
}



and the corresponding entry in configuration xml :


<aop:aspectj-autoproxy/>
<bean id="destroyAspect" class="org.dg.biz.trd.DestroyScope"></bean>



Conclusion

One of the great benefits of custom scopes in Spring 2.0 is the fact that it allows declarative lifecycle management of domain objects without the service location api intruding your business logic code. In the above example, the two wired beans BasketingService and BasketTrade have different lifecycles - yet the collaborating code and the associated business logic is completely oblivious about this difference. The declarative @End annotation, along with the Spring AOP magic, works behind the doors to automatically fetch a new instance of BasketTrade when the user asks for the next access.

1 comment:

Peter Thomas said...

Great stuff! Have you looked at Seam much? I have not, but from your post it really does look like you can match some (if not all) of the widely-talked-about Seam features - with Spring now.