Catálogo de Patrones de Diseño J2EE. Y II: Capas de Negocio y de Integración

La búsqueda y creación de servicios implican interfaces complejos y operaciones de red.

. Problema

Los clientes J2EE interactúan con componentes de servicio, como componentes JavaBeans Enterprise (EJB) y Java Message Service (JMS), que proporcionan servicios de negocio y capacidades de persistencia. Para interactúar con estos componentes, los clientes deben o lcalizar el componente de servicio (referido como una operación de búsqueda) o crear un nuevo componente. Por ejemplo, un cliente EJB debe localizar el objeto home del bean enterprise, que el cliente puede utilizar para encontrar un objeto o para crear uno o más beans enterprise. De forma similar, un cliente JMS primero debe localizar la Factoría de Conexiones JMS para obtener una Conexión JMS o una Sesión JMS.

Todos los clientes de aplicaciones J2EE utilizan la facilidad JNDI para buscar y crear componentes EJB o JMS. El API JNDI permite a los clientes obtener un objeto Contexto Inicial que contiene el nombre del componente a uniones de objetos. El cliente empieza obteniendo el contexto inicial para un objeto home de un bean. El contexto inicial permanece válido mientras sea válida la sesión del cliente. El cliente proporciona el nombre registrado en JNDI del objeto requerido para obtener una referencia a un objeto administrado. En el contexto de una aplicación EJB, un objeto administrado típico es un objeto home de un bean enterprise. Para aplicaciones JMS, el objeto administrado puede ser una Factoría de Conexiones JMS (para un Topic o una Queue) o un Destino JMS (un Topic o una Queue).

Por eso, localizar un objeto servicio administrado JNDI es un tarea común para todos los clientes que necesiten acceder al objeto de servicio. Por ejemplo, es fácil ver que muchos tipos de clientes utilizan repetidamente el servicio JNDI, y que el código JNDI aparece varias veces en esos clientes. Esto resulta en una duplicación de código innecesaria en los clientes que necesitan buscar servicios.

También, crear un objeto de contexto JNDI inicial y realizar una búsqueda para objeto home EJB utiliza muchos recursos. Si varios clientes requieren de forma repetitiva el mismo objeto home de un bean, dicha duplicación de esfuerzos puede impactar de forma negativa en el rendimiento de la aplicación.

Examinemos el proceso de búsqueda y creación de varios componentes J2EE.

  1. La búsqueda y creación de Beans Enterprise trata sobre lo siguiente:
    • Una correcta configuración del entorno JNDI para que se conecte con el servicio de nombrado y directorio utilizado por la aplicación. Esta configuración proporciona la localización del servicio de nombrado y las credenciales de autentificaciones necesarias para acceder a ese servicio.
    • Entonces el servicio JNDI puede proporcionar al cliente un contexto inicial que actúa como un almacen para las uniones de componentes nombre-a-objeto. El cliente solicita a este contexto inicial que busque el objeto EJBHome del bean enterprise requerido proporcionando un nombre JNDI para ese objeto EJBHome.
    • Encontrar el objeto EJBHome utilizando el contexto de búsqueda inicial
    • Después de obtener el objeto EJBHome, crea, elimina o encuentra el bean enterprise utilizando los métodos create, move o find (solo para beans de entidad) del objeto EJBHome.
  2. La búsqueda y creación de componentes JMS (Topic, Queue, QueueConnection, QueueSession, TopicConnection, TopicSession, etc.) implica los siguientes pasos. Observa que en estos pasos, Topic se refiere al modelo de mensajería publica/subscribe y que Queue se refiere al modelo de mensajería punto-a-punto.
    • Una correcta configuración del entorno JNDI para que se conecte con el servicio de nombrado y directorio utilizado por la aplicación. Esta configuración proporciona la localización del servicio de nombrado y las credenciales de autentificaciones necesarias para acceder a ese servicio.
    • Obtener el contexto inicial para el proveedor de servicio JMS desde el servicio de nombrado JNDI.
    • Utilizar el contexto inicial para obtener un Topic o un Queue suministrando el nombre JNDI para ese Topic o Queue. Ambos son objetos JMSDestination.
    • Utilizar el contexto inicial para obtener un TopicConnectionFactory o un QueueConnectionFactory suministrando el nombre JNDI para la factoría adecuada.
    • Usar el TopicConnectionFactory para obtener un TopicConnection o el QueueConnectionFactory para obtener un QueueConnection.
    • Utilizar el TopicConnection para obtener un TopicSession o el QueueConnection para obtener un QueueSession.
    • Utilizar el TopicSession para obtener un TopicSubscriber o un TopicPublisher para el Topic Requerido. Utilizar el QueueSession para obtener un QueueReceiver o un QueueSender para el Queue requerido.

El proceso de búsqueda y creación de componentes implica una implementación de una factoria de contextos suministrada por un vendedor. Esto introduce dependencias del vendedor en los clientes de la aplicación que necesitan utilizar la facilidad de búsqueda JNDI para localizar beans enterprise y componentes JMS.

. Causas

  • Los clientes EJB necesitan utilizar el API JNDI para buscar objetos EJBHome utilizando el nombre registrado del bean enterprise.
  • Los clientes JMS necesitan utilizar el API JNDI para buscar componentes JMS utilizando los nombres registrados en JDNI para esos componentes JMS.
  • La factoría de contextos utilizada para la creación del contexto inicial JNDI la proporciona el vendedor del proveedor del servicio y por lo tanto es dependiente del vendedor. La factoría de contexto también es dependiente del tipo de objeto que se está buscando. El contexto para una JMS es diferente que el contexto para EJBs, con diferentes proveedores.
  • La búsqueda y creación de componentes de servicio podría ser compleja y se podría utilizar repetidamente en múltiples clientes en la aplicación.
  • La creación del contexto inicial y el servicio de búsqueda de objetos, si se utiliza frecuentemente, puede consumir muchos recursos e impactar en el rendimiento de la aplicación. Esto es especialmente cierto si los clientes y los servicios están localizados en diferentes capas.
  • Los clientes EJB podrían necesitar reestablecer conexiones a un ejemplar de bean enterprise al que se ha accedido préviamente, teniendo solamente su objeto Handle.

. Solución

Utilizar un objeto Service Locator para abstraer toda la utilización JNDI y para ocultar las complejidades de la creación del contexto inicial, de busqueda de objetos home EJB y de re-creación de objetos EJB. Varios clientes pueden reutilizar el objeto Service Locator para reducir la complejidad del código, proporcionando un punto de control, y mejorando el rendimiento proporcionando facilidades de caché.

Este patrón reduce la complejidad del cliente que resulta de las dependencias del cliente y de la necesidad de realizar los procesos de búsqueda y creación, que consumen muchos recursos. Para eliminar estos problemas, este patrón proporciona un mecanismo para abstraer todas las dependencias y detalles de red dentro del Service Locator.

. Estructura

La siguiente figura muestra el diagrama de clases que representa las relaciones para el patrón Service Locator.

. Participantes y Responsabilidades

La siguiente figura contiene el diagrama de secuencia que muestra la interacción entre los distintos participantes en el patrón Service Locator.

. Client

Este es el cliente del Service Locator. El cliente es un objeto que normalmente requiere acceder a objetos de negocio como un Business Delegate.

. Service Locator

El Service Locator abstrae el API de los servicios de búsqueda (nombrado), las dependencias del vendedor, las complejidades de la búsqueda, y la creación de objetos de negocio, y proporciona un interface simple para los clientes. Esto reduce la complejidad del cliente. Además, el mismo cliente y otros clientes pueden reutilizar el Service Locator.

. InitialContext

El objeto InitialContext es el punto de inicio para los procesos de búsqueda y creación. Los proveedores de servicio proporcionan el objeto context, que varía dependiendeo del tipo de objetos de negocio proporcionados por el servicio de búsqueda y creación del Service Locator. Un Service Locator que proporciona los servicios para varios tipos de objetos de negocio (como beans enterprise, componentes JMS, etc.) utiliza varios tipos de objetos context, cada uno obtenido de un proveedor diferente (por ejemplo, el proveedor de contexto para un servidor de aplicaciones EJB podría ser diferente del proveedor de contexto para un servicio JMS).

. ServiceFactory

El objeto ServiceFactory representa un objeto que proporciona control del ciclo de vida para objetos BusinessService. El objeto ServiceFactory para beans enterprise es un objeto EJBHome. El ServiceFactory para componentes JMS puede ser un objeto ConnectionFactory, como un TopicConnectionFactory o un QueueConnectionFactory.

. BusinessService

BusinessService es un rol que cumple el servicio que el cliente ha solicitado. El objeto BusinessService se crea, se busca o se elimina mediante el objeto ServiceFactory. El objeto BusinessService en el contexto de una aplicación EJB es un bean enterprise. El objeto BusinessService en el contexto de una aplicación JMS puede ser un TopicConnection o un QueueConnection. TopicConnection y QueueConnection se pueden entonces utilizar para producir un objeto JMSSession, como un TopicSession o un QueueSession respectivamente.

. Estrategias

. EJB Service Locator

El Service Locator para componentes bean enterprise utiliza objetos EJBHome, como un BusinessHome en el rol del ServiceFactory. Una vez obtenido el objeto EJBHome, se puede almacenar en el ServiceLocator para un uso posterior y evitar así otra búsqueda JNDI cuando el cliente necesite de nuevo el objeto home. Dependiendo de la implementación, el objeto home puede ser devuelto al cliente, que puede entonces utilizarlo para buscar, crear o eliminar beans enterprise. Por otro lado, el ServiceLocator puede retener (en el caché) el objeto home y ganar la responsabilidad adicional de hacer de proxy de todas las llamadas de clientes al objeto home. En la siguiente figura podemos ver el diagram de clases para la Estrategia EJB Service Locator:

La interación entre todos los participantes en un Service Locator para un bean enterprise se puede ver en la siguiente figura:

. JMS Queue Service Locator

Esta estrategia se aplica para los requerimientos de mensajería punto-a-punto. El Service Locator para componentes JMS utiliza objetos QueueConnectionFactory en el rol del ServiceFactory. Se busca el QueueConnectionFactory utilizando su nombre JNDI. El ServiceLocator puede almacenar (en el caché) el objeto QueueConnectionFactory para un uso posterior. Esto evita repetidas llamadas JNDI para buscarlo cuando el cliente lo necesite de nuevo. Por otro lado, el ServiceLocator podría enviar el QueueConnectionFactory al cliente. Entonces, el cliente puede utilizarlo para crear una QueueConnection. Se necesita una QueueConnection para poder obtener una QueueSession o para crear un Message, un QueueSender (para enviar mensajes a la cola), o un QueueReceiver (para recibir mensajes de la cola). En la siguiente figura podemos ver el diagrama de clases para esta estrategia. En este diagrama, La cola es un objeto JMS Destination registrado como un objeto JNDI-administered que representa la cola. El objeto Queue se puede obtener directamente desde el contexto buscándolo por su nombre JNDI.

La interacción entre los participantes en un Service Locator para mensajería punto-a-punto utilizando Queues JMS se puede ver en la siguiente figura:

. JMS Topic Service Locator

Esta estraregia es aplicable para requerimientos de mensajeria publica/subscribe. El Service Locator para componentes JMS utiliza objetos TopicConnectionFactory en el rol del ServiceFactory. Se busca el objeto TopicConnectionFactory utilizando sun nombre JNDI. El TopicConnectionFactory se puede almacenar (en el caché) mediante el ServiceLocator para su uso posterior. Esto evita repetidas llamadas JNDI para buscarlo cuando el cliente lo necesite de nuevo. Por otro lado, el ServiceLocator podría enviar el TopicConnectionFactory al cliente. Entonces el cliente puede utilizarlo para crear una TopicConnection. Se necesita una TopicConnection para poder obtener una TopicSession o para crear un Message, un TopicPublisher (para publicar un menaje para un topic), o un TopicSubscriber (para subscribirse a un topic). En la siguiente figura podemos ver el diagrama de clases para la estrategia JMS Topic Service Locator. En este diagrama, el Topic es un objeto JMS Destination registrado como un objeto adiministrado JNDI que representa el topic. El objeto Topic se puede obtener directamente desde el contexto buscándolo por su nombre JNDI.

La interacción entre los participantes en un Service Locator para mensajería pubicar/subscribir utilizando Topics JMS se puede ver en la siguiente figura:

. Combined EJB and JMS Service Locator

Estas estrategias para EJB y JMS se pueden utilizar para proporcionar implementaciones independientes de Service Locator, ya que los clientes para EJB y JMS podría ser más o menos mutuamente exclusivos. Sin embargo, si hay la necesidad de combinar estas estrategias, es posible hacerlo pafra proporcionar el Service Locator para todos los objetos bean enterprise y componentes JMS.

. Type Checked Service Locator

Los dos últimos diagramas proporcionan facilidades de búsqueda pasándole el nombre en el servicio de búsqueda. Para una búsqueda de un bean enterprise, el Service Locator necesita una clase como parámetro del método PortableRemoteObject.narrow(). El Service Locator puede proporcionar un método getHome(), que acepta como argumento el nombre del servicio JNDI y el nombre de la clase del objeto EJBHome del bean enterprise. Utilizando este método de pasar nombres en servicios JNDI y clases de objetos EJBHome se pueden provocar errores en los clientes. Otra aproximación es definir estáticamente los servicios en el ServiceLocator, en lugar de pasarlos como parámetros string, el cliente los pasa como constantes.

Esta estrategia tiene inconvenientes. Reduce la flexibilidad de la búsqueda, que está en la estrategia Services Property Locator, pero añade chequeo de tipos en el paso de una constante al método ServiceLocator.getHome().

. Service Locator Properties

Esta estrategia ayuda a corregir los inconvenientes de la estrategia anterior. Aquí se sugiere el uso de ficheros de propiedades y/o descriptores de ficheros para especificar los nombres JNDI y los nombres de las clases EJBHome. Para los clientes de la capa de presentación, dichas propiedades se pueden especificar en los descriptores de despliegue de la capa de presentación o en ficheros de propiedades. Cuando la capa de presentación accede a la capa de negocio, normalmente utiliza el patrón Business Delegate.

El patrón Business Delegate interactúa con el Service Locator para localizar los componentes de negocio. Si la capa de presentación carga las propiedades durante la inicialización y puede proporcionar un servicio para manejar los nombres JNDI y los nombres de las clases EJB para los beans enterprise requeridos, el Business Delegate podría solicitar a este servicio que los obtuviera. Una vez que el Business Delegate tiene el nombre JNDI y el nombre de la clase EJBHome, puede pedirle al Service Locator el EJBHome pasándole estas propiedades como argumentos.

Entonces el Service Locator puede utilizar el método Class.forName(EJBHome ClassName) para obtener el objeto EJBHome y utilizar el método Portable RemoteObject.narrow() para forzar el objeto, según se verá en el método getHome() en el ejemplo de ServiceLocator. Lo único que cambia es de donde vienen los nombres JNDI y los objetos Class . Por lo tanto, esta estrategia evita introducir en el código los nombres JNDI y proporciona flexibilidad para su despliegue.

. Consecuencias

  • Abstrae la Complejidad
    El patrón Service Locator encapsula la complejidad de este proceso de búsqueda y creación (descrito en el problema) y lo mantiene oculto del cliente. El cliente no necesita tratar con la búsqueda de componentes ni factorías de objetos (EJBHome, QueueConnectionFactory, y TopicConnectionFactory, entre otros) porque se ha delegdo esta responsabilidad en el ServiceLocator.
  • Proporciona a los Clientes un Acceso Uniforme a los Servicios
    El patrón Service Locator abstrae todas las complejidades, como acabamos de ver. Haciendo esto, proporciona un interface muy útil y preciso que todos los clientes pueden utilizar. Este interface asegura que todos los tipos de clientes de la aplicación acceden de forma uniforme a los objetos de negocio, en términos de búsqueda y creación. Esta uniformidad reduce la sobrecarga de desarrollo y mantenimiento.
  • Facilita la Adicción de Nuevos Componentes de Negocio
    Como los clientes de beans enterprise no se preocupan de los objetos EJBHome, es posible añadir nuevos objetos EJBHome para beans enterprise y desplegarlos posteriormente sin impactar en los clientes. Los clientes JMS no se preocupan directamente de las factorías de conexiones JMS, por eso se pueden añadir nuevas factorías sin impactar en los clientes.
  • Mejora el Rendimiento de la Red
    Los clientes no están implicados en la búsqueda JNDI y la creación de objetos (factory/home). Como el Service Locator realiza este trabajo, puede asumir las llamadas de red requeridas para buscar y crear objetos de negocio.
  • Mejora el Rendimiento del Cliente mediante el Caché
    El Service Locator puede poner en un caché los objetos y referencias a objetos del contexto inicial para eliminar actividad JNDI inncesaria que ocurre cuando se obtiene el contexto inicial u otro objetos. Esto mejora el rendimiento de la aplicación.

. Código de Ejemplo

. Implementar el Patrón Service Locator

Aquí podemos ver un ejemplo de código para la implementación del patrón Service Locator.

package corepatterns.apps.psa.util;
import java.util.*;
import javax.naming.*;
import java.rmi.RemoteException;
import javax.ejb.*;
import javax.rmi.PortableRemoteObject;
import java.io.*;

public class ServiceLocator {
  private static ServiceLocator me;
  InitialContext context = null;
    
  private ServiceLocator() 
  throws ServiceLocatorException {
    try {
      context = new InitialContext();
    } catch(NamingException ne) {
      throw new ServiceLocatorException(...);
    }
  }
    
  // Returns the instance of ServiceLocator class
  public static ServiceLocator getInstance() 
  throws ServiceLocatorException {
    if (me == null) {
      me = new ServiceLocator();
    }
    return me;
  }
    
  // Converts the serialized string into EJBHandle 
  // then to EJBObject.
  public EJBObject getService(String id) 
  throws ServiceLocatorException {
    if (id == null) {
      throw new ServiceLocatorException(...);
    }
    try {
      byte[] bytes = new String(id).getBytes();
      InputStream io = new 
        ByteArrayInputStream(bytes);
      ObjectInputStream os = new 
        ObjectInputStream(io);
      javax.ejb.Handle handle = 
        (javax.ejb.Handle)os.readObject();
      return handle.getEJBObject();
    } catch(Exception ex) {
      throw new ServiceLocatorException(...);
    }
  }
    
  // Returns the String that represents the given 
  // EJBObject's handle in serialized format.
  protected String getId(EJBObject session) 
  throws ServiceLocatorException {
    try {
      javax.ejb.Handle handle = session.getHandle();
      ByteArrayOutputStream fo = new 
        ByteArrayOutputStream();
      ObjectOutputStream so = new 
        ObjectOutputStream(fo);
      so.writeObject(handle);
      so.flush();
      so.close();
      return new String(fo.toByteArray());
    } catch(RemoteException ex) {
      throw new ServiceLocatorException(...);
    } catch(IOException ex) {
      throw new ServiceLocatorException(...);
    }
    return null;
  }
    
  // Returns the EJBHome object for requested service 
  // name. Throws ServiceLocatorException If Any Error 
  // occurs in lookup
  public EJBHome getHome(String name, Class clazz) 
  throws ServiceLocatorException {
    try {
      Object objref = context.lookup(name);
      EJBHome home = (EJBHome) 
        PortableRemoteObject.narrow(objref, clazz);
      return home;
    } catch(NamingException ex) {
      throw new ServiceLocatorException(...);
    }
  }
}

. Implementar la Estrategia Type Checked Service Locator

Abajo puedes ver el código de ejemplo para la implementación de la estrategia Type Checked Service Locator:

package corepatterns.apps.psa.util;
// imports
...
public class ServiceLocator {
  // singleton's private instance 
  private static ServiceLocator me;
  
  static {
    me = new ServiceLocator();
  }
    
  private ServiceLocator() {}

  // returns the Service Locator instance 
  static public ServiceLocator getInstance() { 
    return me;
  }

  
  // Services Constants Inner Class - service objects
  public class Services {
    final public static int PROJECT  = 0;
    final public static int RESOURCE = 1;
  }    

  // Project EJB related constants
  final static Class  PROJECT_CLASS = ProjectHome.class;                      
  final static String PROJECT_NAME  = "Project";

  // Resource EJB related constants
  
  final static Class  RESOURCE_CLASS = ResourceHome.class;                        
  final static String RESOURCE_NAME  = "Resource";

  // Returns the Class for the required service 
  static private Class getServiceClass(int service){
    switch( service ) {
      case Services.PROJECT:
        return PROJECT_CLASS;
      case Services.RESOURCE:
      return RESOURCE_CLASS;
    }
    return null;
  }
    
  // returns the JNDI name for the required service 
  static private String getServiceName(int service){
    switch( service ) {
      case Services.PROJECT:
        return PROJECT_NAME;
      case Services.RESOURCE:
        return RESOURCE_NAME;
    }
    return null;
  }
    
  /* gets the EJBHome for the given service using the 
  ** JNDI name and the Class for the EJBHome
  */
  public EJBHome getHome( int s ) 
    throws ServiceLocatorException {
    EJBHome home = null;
    try {
        Context initial  = new InitialContext();

      // Look up using the service name from 
      // defined constant
      Object objref = 
        initial.lookup(getServiceName(s));

      // Narrow using the EJBHome Class from 
      // defined constant
      Object obj = PortableRemoteObject.narrow( 
                objref, getServiceClass(s));
      home = (EJBHome)obj;
    }
    catch( NamingException ex ) {
        throw new ServiceLocatorException(...);
    }
    catch( Exception ex ) {
        throw new ServiceLocatorException(...);
    }
    return home;
  }
}

El código de cliente para utilizar el Service Locator mediante está estrategía, se podría parecer a esto:

public class ServiceLocatorTester {
  public static void main( String[] args ) {
    ServiceLocator serviceLocator = 
      ServiceLocator.getInstance();
    try {
      ProjectHome projectHome = (ProjectHome)
        serviceLocator.getHome(
          ServiceLocator.Services.PROJECT );
    }
    catch( ServiceException ex ) {
      // client handles exception
      System.out.println( ex.getMessage( ));
    }
  }
}

Esta estrategia trata sobre como aplicar el chequeo de tipos a la búsqueda del cliente. Encapsula los valores estáticos del servicio dentro del ServiceLocator y crea una clase Services interna, que declara las constantes del servicio (PROJECT y RESOURCE). El cliente de prueba obtiene un ejemplar del ServiceLocator y llama a getHome(), pasándole PROJECT. Entonces ServiceLocator obtiene el nombre de la entrada JNDI y la clase Home y devuelve el EJBHome.

. Patrones Relacionados

  • Business Delegate
    El patrón Business Delegate utiliza a Service Locator para obtener acceso a los objetos de negocio. Esto separa la complejidad de la localización del servicio del Business Delegate, rebajando el acoplamiento entre ellos e incrementando la manejabilidad.
  • Session Facade
    El patrón Session Facade utiliza a Service Locator para obtener acceso a beans enterprise que están implicados en el flujo de trabajo. Session Facade podría utilizar directamente el patrón Service Locator o delegar el trabajo en un Business Delegate.
  • Transfer Object Assembler
    El patrón Transfer Object Assembler utiliza a Service Locator para obtener acceso a varios beans enterprise que necesita para acceder a construir su objeto Transfer Object compuesto. Este patrón podría utilizar directamente el patrón Service Locator o delegar el trabajo en un Business Delegate.

COMPARTE ESTE ARTÍCULO

ENVIAR A UN AMIGO
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN GOOGLE +
ARTÍCULO ANTERIOR

¡SÉ EL PRIMERO EN COMENTAR!
Conéctate o Regístrate para dejar tu comentario.