Get down to business

Achieve implementation transparency for your business objects with the Business Object Factory framework

Many architects strive to reduce coupling between the presentation tier and the business logic tier in multitier applications. The Business Delegate design pattern (in Sun Microsystems' Core Java 2 Platform, Enterprise Edition (J2EE) Patterns Catalog) was created to solve this problem. But, many critics claim it is an unnecessary added layer of abstraction for achieving this simple goal. Most argue that the business delegate's interface mimics the business logic interface so closely that any change to the business logic interface also mandates a change to the business delegate's interface. In most cases, this is true. However, the Business Delegate pattern has other benefits such as remoteness hiding, failure recovery, thread synchronization, and caching. But many applications that use the Business Delegate pattern do not take advantage of these additional benefits. Thus, the pattern's overhead seems to outweigh its merits. For these applications, all that is desired is an abstraction layer that hides the underlying implementation details, such as the familiar lookup and narrow operations required for Enterprise JavaBeans (EJB) implementations. This article takes you through the API, configuration, and implementation of one such framework, the Business Object Factory framework.

Note: You can download this article's source code from Resources.

The API

The Business Object Factory framework's simplest part is the API itself. There is only one major class, but a few other "behind-the-scenes" classes can extend and customize the framework.

The BusinessObjectFactory class

Apart from the business interfaces used in their applications, consumers of business objects' services need only concern themselves with one primary class, BusinessObjectFactory. Let's look at its public interface:

public final class BusinessObjectFactory
{
  public BusinessObjectFactory getInstace();
  public Remote create( Class businessInterface );
}

As you can see, the BusinessObjectFactory class implements the Singleton design pattern (a Gang of Four design pattern). The BusinessObjectFactory's constructor is declared as private, and the class only maintains one static instance. This provides one global point of access to the functionality BusinessObjectFactory provides. We could just as easily make all of the methods static, but that approach is more object-oriented.

The create() method tends to raise a few eyebrows. First, it returns an instance of java.rmi.Remote. Most business object implementation choices, namely Remote Method Invocation (RMI), EJB, and the Java API for XML-based Remote Procedure Calls (JAX-RPC), require a remote interface. For simple JavaBean-based business object implementations, this adds the inconvenience of requiring the client code to catch java.rmi.RemoteException when it will never be thrown. However, this inconvenience helps provide the implementation transparency, as the business objects can be migrated to a different implementation type without altering the client code. The only stipulation is that the remote interface does not change. Another create() method peculiarity is its parameter type, java.lang.Class, which corresponds to the remote interface type requested. This allows the BusinessObjectFactory class to be loosely coupled with the application using it. This does not prevent company XYZ from creating an adapter class to simplify client programming:

public class XyzBusinessObjectFactory
{
  // Reference to singleton used for brevity, not required!
  private BusinessObjectFactory bof = BusinessObjectFactory.getInstance();
  
  public AccountServices createAccountServices()
  {
    return ( AccountServices )bof.create( AccountServices.class );
  }
}

In the above code, the XyzBusinessObjectFactory class encapsulates the required type casting involved when using the BusinessObjectFactory class and makes client coding much cleaner and type-safe.

Also notice that none of the BusinessObjectFactory class's methods declare that they throw any checked exceptions (those that directly extend the java.lang.Exception class). The implementation will throw runtime exceptions (those that extend from java.lang.RuntimeExceptions) when problems occur, as these problems are typically indicative of a configuration issue and not necessarily an error within the application logic itself. The use of runtime exceptions greatly simplifies the client code, as runtime exceptions do not require try/catch blocks, but still allows for customized error trapping if required. When used in a Web application, a servlet filter or a customized error page defined in the web.xml file can be employed to catch these exceptions. The Java Data Objects (JDO) API uses a similar approach.

Another interesting note about the BusinessObjectFactory class is that it is declared final. This rules out the option of using the Abstract Factory-Factory Method design pattern combination so typical in implementation-hiding frameworks, such as the Data Access Object design pattern. However, as you'll see, the Business Object Factory framework's extensibility isn't provided by subclasses, but through delegation.

The BusinessObjectFactoryDelegate class

The BusinessObjectFactory class delegates business object creation to one of its BusinessObjectFactoryDelegate instances:

public abstract class BusinessObjectFactoryDelegate
{
  protected abstract Remote doCreate();
  public final void setBusinessInterface( Class businessInterface );
  public final Class getBusinessInterface();
}

BusinessObjectFactory instances create business object instances that implement a specific remote business interface, represented by the businessInterface property. You can imagine different BusinessObjectFactoryDelegate implementation flavors. Each flavor would represent another possible business object implementation choice, such as JavaBeans, RMI, EJB, or JAX-RPC. I implement some examples later. Notice that client code does not need to be exposed to any BusinessObjectFactoryDelegate instances. The BusinessObjectFactory class takes care of all of the delegation internally, as we shall soon see. This is not the framework's only hidden feature.

The InvocationDecorator interface

Client code sometimes uses another aspect of the Business Object Factory framework without even knowing it. Each invocation of a business interface can be optionally "decorated" by instances of the InvocationDecorator interface:

public interface InvocationDecorator
{
  public Object decorate( Method method, Object[] args, InvocationDecoratorChain chain ) throws Throwable;
}

The InvocationDecorator instances are given a chance to intercept each method invocation and add decorations to it. Some potential decorations include logging, transaction management, and authorization. Each InvocationDecorator instance can perform some operation and then pass control to the instance of InvocationDecoratorChain passed in as a parameter:

public interface InvocationDecoratorChain
{
  public Object decorate( Method method, Object[] args ) throws Throwable;
}

InvocationDecoratorChain objects track an ordered list of the InvocationDecorator instances and eventually call the desired method on the target business object. For those of you with Java Servlet API experience, this decorator idea seems strikingly similar to servlet filter support introduced in the Servlet API 2.3 specification. In fact, the idea is specifically borrowed from that specification. One consideration came to mind as I designed this specific part of the framework. Why not use aspect-oriented programming (AOP) and AspectJ to decorate each invocation? However, AOP requires recompilation to add aspects to your code. But, InvocationDecorator instances can be added to (and removed from) the InvocationDecoratorChain by merely changing the configuration file.

Configuration

A very simple XML file (aptly named BusinessObjectFactory.xml) configures the Business Object Factory framework. You can configure all delegates and invocation decorators by adding specific elements to the configuration file:

<business-objects>
  <delegate className="bof.delegates.BeanDelegate" businessInterface="xyz.IBusinessObject">
    <property name="beanClass" value="xyz.BusinessObjectImpl" />
    <decorator className="bof.decorators.CommonsLoggingDecorator" >
      <property name="logName" value="xyz.BusinessObjectImpl" />
    </decorator>
  </delegate>
</business-objects>

This example's delegate element declares that an instance of bof.delegates.BeanDelegate, which we'll see later, constructs all business objects implementing the xyz.IBusinessObject interface. The decorator element indicates that an instance of bof.decorators.CommonsLoggingDecorator, which we'll also see later, should be added to the invocation decorator chain for the aforementioned delegate. Also, the nested property elements set the object's properties represented by the enclosing element. For example, the bof.delegates.BeanDelegate's beanClass would be set to the xyz.BusinessObjectImpl class. Likewise, the bof.decorators.CommonsLoggingDecorator instance's logName property would be set to the java.lang.String value, xyz.BusinssObjectImpl.

Implementation

First, I show how the BusinessObjectFactory class actually performs the delegation to its constituent delegates. Remember that each delegate is responsible for creating objects implementing a specific business interface, represented by a java.lang.Class instance. Therefore, you must maintain a mapping between Class objects and BusinessObjectFactoryDelegate instances. For this, you use a java.util.Map object:

public class BusinessObjectFactory 
{
  private Map delegateMap = new HashMap();
  
  private BusinessObjectFactory()
  {
        // Prevents instantiation by other classes.  Enforces the Singleton design pattern.
  }
    
  private void lock()
  {
    delegateMap = Collections.unmodifiableMap( delegateMap );
  }
  
  public Remote create( Class businessInterface )
  {
      if( businessInterface == null )
      {
          throw new IllegalArgumentException( "Cannot pass null java.lang.Class reference to BusinessObjectFactory.create() method." );
      }
      if( !businessInterface.isInterface() )
      {
          throw new IllegalArgumentException( "Parameter to BusinessObjectFactory.create() method must be an interface." );
      }
      if( !Remote.class.isAssignableFrom( businessInterface ) )
      {
          throw new IllegalArgumentException( "Interface parameter to BusinessObjectFactory.create() method must extend java.rmi.Remote." );
      }
      return getDelegate( businessInterface ).create();
  }
    private BusinessObjectFactoryDelegate getDelegate( Class businessInterface )
    {
      final BusinessObjectFactoryDelegate delegate = ( BusinessObjectFactoryDelegate ) delegateMap.get( businessInterface );
      if( delegate == null )
      {
          throw new ApplicationResourceException( "No delegate defined for interface " + businessInterface.getName() );
      }
      return delegate;
    }
    private void addDelegate( BusinessObjectFactoryDelegate delegate )
    {
      delegateMap.put( delegate.getBusinessInterface(), delegate );
    }
}

In the above code, I included only the BusinessObjectFactory class's instance (nonstatic) methods. I discuss the static implementation methods in the "Load the Configuration File" section below. The ApplicationResourceException class is a runtime exception class that indicates some dependent resource is missing (no configuration file) or corrupt (insufficient configuration information). As you can see, the delegateMap object looks up the appropriate BusinessObjectFactoryDelegate instance. The create() method merely delegates creation to the create() method of the BusinessObjectFactoryDelegate instance returned from the getDelegate() method. The create() method's name is not a misprint. You'll soon see that the create() method is a package-private method that in turn calls the abstract doCreate() method listed earlier. The lock() method prevents any subsequent calls to the addDelegate() method to fail, since the delegateMap object becomes unmodifiable. However, how do the BusinessObjectFactoryDelegate objects get into the delegateMap object in the first place?

Load the configuration file

The Apache Software Foundation provides an open source library, Jakarta Commons Digester, perfectly suited for loading XML configuration files. Documentation about the Digester library is outside this article's scope, but a thorough explanation of the Commons Digester can be found on its homepage (see Resources). The discussion that follows assumes a basic understanding (rules, the object stack, etc.) of the Digester library. Here, I just show how you use the Digester library to load the configuration information:

1 2 3 Page 1
Page 1 of 3