Thursday, October 15, 2009

EJB 2.x maximum performances and flexibility: abstract from Remote vs Local

I think the following should be a best practice whenever you should implement EJB 2.x specifications as this will allow to transparently switch between local and remote interfaces without any change.

In EJB 2.x you need to write the following classes/interfaces to support both remote and local deployment:
  • public class MyComponentBean implements javax.ejb.SessionBean
  • public interface MyComponentRemoteHome extends javax.ejb.EJBRemoteHome
  • public interface MyComponentRemote extends javax.ejb.EJBObject
  • public interface MyComponentLocalHome extends javax.ejb. EJBLocalHome
  • public interface MyComponentLocal extends javax.ejb.EJBLocalObject
If you want to be able to switch between local and remote deployment you need to change every place where you retrieve the EJB, usually this action is concentrated in a ServiceLocator implementation like:

public class MyComponentServiceLocator {
public final static String MY_COMPONENT_LOCATION = "ejb/myComponent";

public static MyComponentLocal getLocal(Properties properties) throws Exception {
       InitialContext context = new InitialContext(properties);
       MyComponentLocalHome home = (MyComponentRemoteHome)context.lookup(MY_COMPONENT_LOCATION + "/local");
       return home.create();
   }
   public static MyComponentRemote getRemote(Properties properties) throws Exception {
       InitialContext context = new InitialContext(properties);
       MyComponentRemoteHome home = (MyComponentRemoteHome)context.lookup(MY_COMPONENT_LOCATION + "/remote");
       return home.create();
   }
}


With this approach you can switch from local to remote just switching from MyComponentServiceLocator.getLocal(...) to MyComponentServiceLocator.getRemote(...) on every place you need to switch, but in addition you need to switch the type you declared for the variable to which you are going to assign the MyComponentServiceLocator call result: from MyComponentLocal to MyComponentRemote.

In addition you need to manually track down all interfaces are exposing the same methods.
Wouldn't it easier if we can have some sort of automatic check and avoid the need to switch the code? Couldn't be possible to switch between local and remote at deployment time without any change at compile time?

Well, the answer is in the following structure:
  • public interface MyComponent
    declares all shared functional methods, each method will throws java.rmi.RemoteException in addition to any exception it should normally throw
  • public interface MyComponentHome
    declares all shared creation methods throwing both java.rmi.RemoteException and javax.ejb.CreateException

  • public class MyComponentBean implements javax.ejb.SessionBean, MyComponentRemote, MyComponentLocal
    the MyComponentRemote and MyComponentLocal interfaces have been added here to ensure the implementation class provides code for all the declared methods: be careful, all methods must not throw java.rmiRemoteException
  • public interface MyComponentRemoteHome extends javax.ejb.EJBRemoteHome, MyComponentHome
    the MyComponentHome interface has been added here to ensure all shared creation methods are supported by the remote home: if all the remote creation methods are shared than this interface will be completely empty!
  • public interface MyComponentRemote extends javax.ejb.EJBObject, MyComponent
    the MyComponent interface has been added here to ensure all the shared methods are supported through the remote interface: if all the remote methods are shared than this interface will be completely empty!
  • public interface MyComponentLocalHome extends javax.ejb. EJBLocalHome, MyComponentHome
    the MyComponentHome interface has been added here to ensure all shared creation methods are supported by the local home: all methods defined in the MyComponentHome interface must be overriden here to remove the java.rmi.RemoteException declaration; if you forget this step your application server should warn you when you deploy this EJB.
  • public interface MyComponentLocal extends javax.ejb.EJBLocalObject, MyComponent
    the MyComponent interface has been added here to ensure all shared methods are supported through the local interface: all methods defined in the MyComponent interface must be overriden here to remove the java.rmi.RemoteException declaration; if you forget this step your application server should warn you when you deploy this EJB.
This is harder to say than to put in practice and the advantages are:
  • one place for shared creation methods: if you add a method to MyComponentHome interface you automatically get it on the remote home and if you forget to override it in the local home (to remove the java.rmi.RemoteException) your application server will warn you on your first deployment;
  • one place for shared functional methods: if you add a method to MyComponent interface you automatically get it on the remote interface and if you forget to override it in the local interface (to remove the java.rmi.RemoteException) your application server will warn you on your first deployment;
  • your clients will no more have to deal with remote or local differences as they will use the MyComponent interface (unless they need some methods not available on both interfaces)
  • you can still produce different interfaces for local and remote deployments;
  • your implementation will always implement the required methods;
  • you can switch between local and remote deployment using the ejb-ref directive (in your web.xml or in your ejb.xml)
  • you can have a ServiceLocator like the following one which completely masks the remote vs local

public class MyComponentServiceLocator {
   public final static String MY_COMPONENT_LOCATION = "ejb/myComponent";
   public static MyComponent get(Properties properties) throws NamingException, CreateException, RemoteException {
        InitialContext context = new InitialContext(properties);
       MyComponentHome home = (MyComponentHome)context.lookup("java:comp/env/" + MY_COMPONENT_LOCATION);
       return home.create();
   }
}


If you don't want to deal with the ejb-ref at all you can consider the following ServiceLocator implementation which allows any deployment combination and automatically uses the local interface if available (with lesser performances as two JNDI lookups are performed in the worst case)

public class MyComponentServiceLocator {
   public final static String MY_COMPONENT_LOCATION = "ejb/myComponent";
   public static MyComponent get(Properties properties) throws NamingException, CreateException, RemoteException {
       try {
            return MyComponentServiceLocator.getLocal(properties);
       } catch (Exception e) {
            return MyComponentServiceLocator.getRemote(properties);
       }
   }
   public static MyComponentLocal getLocal(Properties properties) throws NamingException, CreateException {
       InitialContext context = new InitialContext(properties);
       MyComponentLocalHome home = (MyComponentRemoteHome)context.lookup(MY_COMPONENT_LOCATION + "/local");
       return home.create();
   }
   public static MyComponentRemote getRemote(Properties properties) throws NamingException, CreateException, RemoteException {
       InitialContext context = new InitialContext(properties);
       MyComponentRemoteHome home = (MyComponentRemoteHome)context.lookup(MY_COMPONENT_LOCATION + "/remote");
       return home.create();
   }
}

Be careful, the last solution can produce unwanted exception traces in your application server when the local lookup fails: those exceptions are normal unless produced by the remote lookup. Those unwanted exceptions can bring you mad when you try to understand why your EJB is not working.

No comments: