The Service Pattern

Spif store chains gets you a long way in separating infrastructure from business logic. Typically, tree fourths of what is normally session bean methods or similar gets superfluous using Spif. The Service pattern describes how to organize the remaining service methods.

Background

Service methods are the top-level methods of a component, the methods which offers starting points into the object model of a component. Service methods are typically facades, hosted in session beans in J2EE applications, or managers in traditional OO applications.

Hosting service methods in session beans has the usual drawbacks of tying infrastructure and business logic. The business logic can not be run without an application server employing distribution mechanics - not even for testing, the business logic can not be used with other infrastructure, and in the case of J2EE - calling the service methods takes a lot of code.

Several patterns have been proposed in the J2EE community to solve this. The Business Delegate pattern employs a client which forwards (not delegates) to the session bean. This makes the service method easier to access, but does not make the business logic any more infrastructure independent. The EJB Command pattern employs a generic invocation session bean and defines each service method in a separate command class which can be shipped to the server for execution by the invocation bean. This does a better job at separating the infrastructure and business logic, but the drawbacks are sizeable. Declarative security and transactions can not be used as there is only one session bean method, and there is no clean component service api, as the service class is fragmented into command classes. A simple alternative which makes services both easily accessible and separated from infrastructure is the Service pattern.

The pattern

The service pattern collects the service methods of a component in a plain Java pluggable singleton. A pluggable singleton is a relaxed singleton where the goal is not to enforce the existence of one single instance, but to provide one official instance for all clients to use. The official instance may be a subclass of the service, changing some functionality transparently to clients as we'll see below. The service class of component X is customary called XService. An example follows:

public class ProductService {

  private static ProductService instance=new ProductService();

  protected ProductService() {
  }

  /** Returns the instance to use by clients */
  public static ProductService get() {
    return instance;
  }

  /** Sets the instance to use by clients */
  public static void set(ProductService service) {
    instance=service;
  }

  // Service methods follows

  public Product[] getBaseProducts() {
    // ...Pure business implementation
  }

  // ...and so on
 
}

So this is the base implementation of the service methods. If we want to access this service remotely in J2EE, we create a session bean handling that infrastructural concern and forwarding to the base implementation:

public class ProductServiceBean implements SessionBean {

  public Product[] getBaseProducts() {
    try {
      // Do server-entering bookkeeping or whatever
      ProductService.get().getBaseProducts();
    }
    catch (RuntimeException e) {
      // Log with all relevant state here, before we leave the server
      throw e;
    }
  }

  //....The EJB callbacks

}

In addition we need a client object calling the bean. The client object extends the base service, overriding methods which must be executed at the server and leaves the rest of the methods untouched.

public class ProductServiceClient extends ProductService {

  /** Clients should be able to instantiate and make official */
  public ProductServiceClient() {
  }

  // Override any service method which can not be executed locally

  public Product[] getBaseProducts() {
    try {
	  return ProductServiceBeanAccessor.getRemote().getBaseProducts();
    }
    catch (RemoteException e) {
      throw getUnwrappedRuntimeException(e);
    }
  }

}

The last class makes it equally easy to access a service at the client as at the server. In fact, code using the service will function equally if they are excuting the service locally or remotely. To make some business code execute towards a remote ProductService rather than a local one, some client initializing code just has to say

ProductService.set(new ProductServiceClient());

Making remote calls transparent is not the same as ignoring the performance issues inherent in remote invocations. In fact, every component service invocation should be treated as a possible remote call. By returning real business objects from the service methods however, business logic may be transferred to the client, which limits the amount of logic which has to be invokes as service methods. The remaining service methods are usually concerned with returning entry-point objects and returning time-consuming reports.

Extension for persistent components

Components exposing persistent objects will benefit from using the pluggable service to separate the persistence code from the business logic. Make a subclass of the service where persistence-specific code not handled by the Spif chain is placed. For the product service this would look as follows:

public class ProductServicePersistence extends ProductService{

  public ProductService() {
  }

  / Override the methods which requires persistence code onlt

  public Product[] getBaseProducts() {
    // ...select ids from the db, instantiate using SPIF and return
  }

  // ...and so on
 
}

It is often beneficial to create non-persistence versions in the base service of all such methods, even if they will not be used in production. This is often a simple matter, and both automatic and manual testing is made a lot easier if the entire application can be run without a db.

In summary, the Service patterns advocates a simple partitioning of infrastructural and business concerns for component entry-point methods. By organizing the partition as described, the infrastructural context a service run in is transparent to invokers.

SourceForge Logo